Announcement

Collapse
1 of 2 < >

New Sub-Forum

In an effort to help make sure there are appropriate categories for topics of discussion that are happening, there is now a sub-forum for databases and database programming under Special Interest groups. Please direct questions, etc., about this topic to that sub-forum moving forward. Thank you.
2 of 2 < >

Forum Guidelines

This forum is for finished source code that is working properly. If you have questions about this or any other source code, please post it in one of the Discussion Forums, not here.
See more
See less

Task Manager Emulator

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Task Manager Emulator

    This program displays a listview with information on all running processes, retrieved from NtQuerySystemInformation. It will probably only run on Win2K or greater.

    The columns can be sorted and also reordered (via drag and drop). You can track/untrack a certain process by selecting a row, which will be shown in yellow. Data that has changed inbetween refreshes will be shown in green or blue, depending on if the data has decreased or increased.

    You may do whatever you want with the code. Enjoy.

    Code:
    #PBFORMS CREATED V1.51
    #COMPILE EXE
    #DIM ALL
    #OPTION VERSION5  'Win2000(NT5), WinXP(NT5.1), WinVista(NT6.0)
    
    #INCLUDE "WIN32API.INC"
    
    $Program = "Task Manager Emulator"
    
    
    #PBFORMS BEGIN INCLUDES
    %USEMACROS = 1
    #IF NOT %DEF(%WINAPI)
        #INCLUDE "WIN32API.INC"
    #ENDIF
    #IF NOT %DEF(%COMMCTRL_INC)
        #INCLUDE "COMMCTRL.INC"
    #ENDIF
    #INCLUDE "PBForms.INC"
    #PBFORMS END INCLUDES
    
    #PBFORMS BEGIN CONSTANTS
    %IDC_Listview      = 1001
    %IDD_TaskManDialog =  101
    #PBFORMS END CONSTANTS
    
    
    DECLARE CALLBACK FUNCTION ShowTaskManDialogProc()
    DECLARE FUNCTION ShowTaskManDialog(BYVAL hParent AS DWORD) AS LONG
    #PBFORMS DECLARATIONS
    
    
    %TIMERID = 2000
    %WM_REFRESHLISTVIEW = 2001    'message to the dialog to refresh the listview
    
    %ListviewColumnCount = 24
    '-- assign a number to each column, numbering them in the desired starting order:
    'FYI: the listview will be initialized with %LVS_EX_HEADERDRAGDROP to allow the user to rearrange the columns via drag and drop
    %ColumnNumber_Name                = 0
    %ColumnNumber_ID                  = 1
    %ColumnNumber_ParentProcessID     = 2
    %ColumnNumber_CreateTime          = 3
    %ColumnNumber_RunningTime         = 4
    %ColumnNumber_UserTime            = 5
    %ColumnNumber_KernelTime          = 6
    %ColumnNumber_TotalCPUTime        = 7
    %ColumnNumber_CPUPercent          = 8
    %ColumnNumber_BasePriority        = 9
    %ColumnNumber_HandleCount         = 10
    %ColumnNumber_NumberOfThreads     = 11
    %ColumnNumber_PeakVirtualSize     = 12
    %ColumnNumber_VirtualSize         = 13
    %ColumnNumber_PageFaultCount      = 14
    %ColumnNumber_PeakWorkingSetSize  = 15
    %ColumnNumber_WorkingSetSize      = 16
    %ColumnNumber_PrivatePageCount    = 17
    %ColumnNumber_ReadOperationCount  = 18
    %ColumnNumber_WriteOperationCount = 19
    %ColumnNumber_OtherOperationCount = 20
    %ColumnNumber_ReadTransferCount   = 21
    %ColumnNumber_WriteTransferCount  = 22
    %ColumnNumber_OtherTransferCount  = 23
    
    
    
    DECLARE FUNCTION NtQuerySystemInformation LIB "ntdll.dll" ALIAS "NtQuerySystemInformation" (BYVAL SystemInformationClass AS DWORD, BYVAL lpvBuffer AS DWORD, BYVAL dwBufferSize AS DWORD, BYVAL unFlag2 AS DWORD) AS LONG
    
    %SystemProcessesAndThreadsInformation = 5   'the SystemInformationClass number to send to NtQuerySystemInformation
    
    'NtQuerySystemInformation return values:
    %STATUS_SUCCESS              = &h00000000
    %STATUS_INFO_LENGTH_MISMATCH = &hC0000004
    
    TYPE CLIENT_ID  'used in the SYSTEM_THREAD_INFORMATION struct
        UniqueProcess AS DWORD
        UniqueThread  AS DWORD
    END TYPE
    
    TYPE UNICODE_STRING  'used in the SYSTEM_PROCESS_INFORMATION struct
        Length        AS WORD  'USHORT - Length in bytes of string in Buffer
        MaximumLength AS WORD  'USHORT - Maximum length in bytes of Buffer
        pBuffer       AS DWORD 'PWSTR - Pointer to unicode string
    END TYPE
    
    TYPE VM_COUNTERS  'used in the SYSTEM_PROCESS_INFORMATION struct    (VM_COUNTERS_EX includes PrivatePageCount)
        PeakVirtualSize            AS DWORD 'SIZE_T
        VirtualSize                AS DWORD 'SIZE_T - this is NOT the same as Virtual Memory Size in Task Manager (according to Process Explorer)
        PageFaultCount             AS DWORD 'ULONG
        PeakWorkingSetSize         AS DWORD 'SIZE_T
        WorkingSetSize             AS DWORD 'SIZE_T
        QuotaPeakPagedPoolUsage    AS DWORD 'SIZE_T
        QuotaPagedPoolUsage        AS DWORD 'SIZE_T
        QuotaPeakNonPagedPoolUsage AS DWORD 'SIZE_T
        QuotaNonPagedPoolUsage     AS DWORD 'SIZE_T
        PagefileUsage              AS DWORD 'SIZE_T
        PeakPagefileUsage          AS DWORD 'SIZE_T
        PrivatePageCount           AS DWORD 'SIZE_T <-- this "extra" item seems to be needed, or else the IO_COUNTERS will not line up correctly
    END TYPE
    
    TYPE IO_COUNTERS  'used in the SYSTEM_PROCESS_INFORMATION struct
        ReadOperationCount  AS QUAD 'LARGE_INTEGER
        WriteOperationCount AS QUAD 'LARGE_INTEGER
        OtherOperationCount AS QUAD 'LARGE_INTEGER
        ReadTransferCount   AS QUAD 'LARGE_INTEGER
        WriteTransferCount  AS QUAD 'LARGE_INTEGER
        OtherTransferCount  AS QUAD 'LARGE_INTEGER
    END TYPE
    
    TYPE SYSTEM_THREAD_INFORMATION  'used in the SYSTEM_PROCESS_INFORMATION struct
        ftKernelTime         AS QUAD 'FILETIME 'LARGE_INTEGER - time spent in kernel mode
        ftUserTime           AS QUAD 'FILETIME 'LARGE_INTEGER - time spent in user mode
        ftCreationTime       AS QUAD 'FILETIME 'LARGE_INTEGER - thread creation time
        dwWaitTime           AS DWORD 'ULONG - wait time
        StartAddress         AS DWORD 'PVOID - start address
        ClientID             AS CLIENT_ID 'thread and process IDs
        Priority             AS LONG  'KPRIORITY - dynamic priority
        BasePriority         AS LONG  'KPRIORITY - base priority
        dwContextSwitchCount AS DWORD 'ULONG - number of context switches
        STATE                AS LONG  'current state
        WaitReason           AS LONG  'wait reason
    END TYPE
    
    TYPE SYSTEM_PROCESS_INFORMATION
        NextEntryOffset  AS DWORD 'ULONG - offset to the next entry
        NumberOfThreads  AS DWORD 'ULONG - number of threads
        dwReserved1      AS QUAD  'reserved
        dwReserved2      AS QUAD  'reserved
        dwReserved3      AS QUAD  'reserved
        CreateTime       AS QUAD  'LARGE_INTEGER / FILETIME - process creation time
        UserTime         AS QUAD  'LARGE_INTEGER / FILETIME - time spent in user mode
        KernelTime       AS QUAD  'LARGE_INTEGER / FILETIME - time spent in kernel mode
        ProcessName      AS UNICODE_STRING 'process name
        BasePriority     AS LONG  'KPRIORITY - base process priority
        ProcessID        AS DWORD 'ULONG - process identifier
        ParentProcessID  AS DWORD 'ULONG - parent process identifier
        HandleCount      AS DWORD 'ULONG - number of handles
        SessionID        AS DWORD 'reserved?
        UniqueProcessKey AS DWORD 'reserved?
        VMCounters       AS VM_COUNTERS 'virtual memory counters
        IoCounters       AS IO_COUNTERS 'i/o counters (NT 4.0 does not have this!)
        'DWORD dCommitCharge;   // bytes        '<--- does this go here?  don't know; don't care, for now, since SYSTEM_THREAD_INFORMATION isn't used by this program anyway
        Threads(1) AS SYSTEM_THREAD_INFORMATION 'threads    (not sure if this is right..)
        'Threads AS SYSTEM_THREAD_INFORMATION 'threads  (maybe this is right?)
    END TYPE
    
    '-- process data arrays - these store the raw data gathered from NtQuerySystemInformation:
    GLOBAL gProcess_NumberOfThreads() AS DWORD
    GLOBAL gProcess_CreateTime()      AS QUAD
    GLOBAL gProcess_UserTime()        AS QUAD
    GLOBAL gProcess_KernelTime()      AS QUAD
    GLOBAL gProcess_Name()            AS STRING
    GLOBAL gProcess_BasePriority()    AS LONG
    GLOBAL gProcess_ID()              AS DWORD
    GLOBAL gProcess_ParentProcessID() AS DWORD
    GLOBAL gProcess_HandleCount()     AS DWORD
    
    GLOBAL gProcess_PeakVirtualSize()            AS DWORD
    GLOBAL gProcess_VirtualSize()                AS DWORD
    GLOBAL gProcess_PageFaultCount()             AS DWORD
    GLOBAL gProcess_PeakWorkingSetSize()         AS DWORD
    GLOBAL gProcess_WorkingSetSize()             AS DWORD
    GLOBAL gProcess_QuotaPeakPagedPoolUsage()    AS DWORD
    GLOBAL gProcess_QuotaPagedPoolUsage()        AS DWORD
    GLOBAL gProcess_QuotaPeakNonPagedPoolUsage() AS DWORD
    GLOBAL gProcess_QuotaNonPagedPoolUsage()     AS DWORD
    GLOBAL gProcess_PagefileUsage()              AS DWORD
    GLOBAL gProcess_PeakPagefileUsage()          AS DWORD
    GLOBAL gProcess_PrivatePageCount()           AS DWORD
    
    GLOBAL gProcess_ReadOperationCount()  AS QUAD
    GLOBAL gProcess_WriteOperationCount() AS QUAD
    GLOBAL gProcess_OtherOperationCount() AS QUAD
    GLOBAL gProcess_ReadTransferCount()   AS QUAD
    GLOBAL gProcess_WriteTransferCount()  AS QUAD
    GLOBAL gProcess_OtherTransferCount()  AS QUAD
    
    '-- sorting arrays:
    GLOBAL gProcess_Index() AS LONG
    GLOBAL gSortedIndex()   AS LONG
    GLOBAL gColumnSortDirection() AS LONG 'toggles between 0 and -1 (ascending and descending) for each column.
    
    '-- calculated arrays - how long a process has been running, and its CPU usage:
    GLOBAL gProcess_RunningTime()        AS QUAD
    GLOBAL gProcess_TotalCPUTime()       AS QUAD
    GLOBAL gProcess_TotalCPUTime_Delta() AS QUAD
    GLOBAL gProcess_CPUPercent()         AS SINGLE
    
    '-- delta arrays - these are used to indicate changes (via color) in the listview:
    ' LONGs are used in place of DWORDs since deltas can be positive or negative.
    GLOBAL gProcess_NumberOfThreads_Delta() AS LONG 'DWORD
    GLOBAL gProcess_UserTime_Delta()        AS QUAD
    GLOBAL gProcess_KernelTime_Delta()      AS QUAD
    GLOBAL gProcess_CPUPercent_Delta()      AS SINGLE
    GLOBAL gProcess_BasePriority_Delta()    AS LONG
    GLOBAL gProcess_HandleCount_Delta()     AS LONG 'DWORD
    
    GLOBAL gProcess_PeakVirtualSize_Delta()            AS LONG 'DWORD
    GLOBAL gProcess_VirtualSize_Delta()                AS LONG 'DWORD
    GLOBAL gProcess_PageFaultCount_Delta()             AS LONG 'DWORD
    GLOBAL gProcess_PeakWorkingSetSize_Delta()         AS LONG 'DWORD
    GLOBAL gProcess_WorkingSetSize_Delta()             AS LONG 'DWORD
    GLOBAL gProcess_QuotaPeakPagedPoolUsage_Delta()    AS LONG 'DWORD
    GLOBAL gProcess_QuotaPagedPoolUsage_Delta()        AS LONG 'DWORD
    GLOBAL gProcess_QuotaPeakNonPagedPoolUsage_Delta() AS LONG 'DWORD
    GLOBAL gProcess_QuotaNonPagedPoolUsage_Delta()     AS LONG 'DWORD
    GLOBAL gProcess_PagefileUsage_Delta()              AS LONG 'DWORD
    GLOBAL gProcess_PeakPagefileUsage_Delta()          AS LONG 'DWORD
    GLOBAL gProcess_PrivatePageCount_Delta()           AS LONG 'DWORD
    
    GLOBAL gProcess_ReadOperationCount_Delta()  AS QUAD
    GLOBAL gProcess_WriteOperationCount_Delta() AS QUAD
    GLOBAL gProcess_OtherOperationCount_Delta() AS QUAD
    GLOBAL gProcess_ReadTransferCount_Delta()   AS QUAD
    GLOBAL gProcess_WriteTransferCount_Delta()  AS QUAD
    GLOBAL gProcess_OtherTransferCount_Delta()  AS QUAD
    
    
    
    
    
    '============================================================================================================
    '== PBMAIN ==================================================================================================
    '============================================================================================================
    FUNCTION PBMAIN()
        LOCAL hModule AS DWORD
        hModule = GetModuleHandle("NTDLL.DLL")
        IF hModule = %NULL THEN
            MSGBOX "GetModuleHandle(NTDLL.DLL) failed.", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
            EXIT FUNCTION
        ELSEIF GetProcAddress(hModule, "NtQuerySystemInformation") = %NULL THEN 'is NtQuerySystemInformation unavailable for this computer?
            MSGBOX "This program will not run on this computer, because NtQuerySystemInformation is unavailable.", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
            EXIT FUNCTION
        END IF
    
        PBFormsInitComCtls (%ICC_WIN95_CLASSES OR %ICC_DATE_CLASSES OR %ICC_INTERNET_CLASSES)
    
        ShowTaskManDialog %HWND_DESKTOP
    END FUNCTION
    
    
    
    
    '============================================================================================================
    '== CONVERTSECONDSINTOFORMATTEDSTRING =======================================================================
    '============================================================================================================
    ' Converts Elapsed Seconds to Elapsed Time (DDdHHhMMmSSsZZZms)   This function is probably overkill (FORMAT$ could be used instead, but this function is faster)
    FUNCTION ConvertSecondsIntoFormattedString (BYVAL timediff AS DOUBLE) AS STRING
        LOCAL milliseconds, seconds, minutes, hours, days AS LONG
        LOCAL sTime AS STRING
        LOCAL a AS BYTE PTR
    
        'sTime = "00d00h00m00s" ' 12 chars
        sTime = "00d00h00m00s000ms" ' 17 chars
        'sTime = "00:00:00:00.000" ' 15 chars
    
        a = STRPTR(sTime)
    
        seconds = FIX(timediff) 'total seconds
        minutes = seconds \ 60  'total mins
        hours   = minutes \ 60  'total hrs
        days    = hours \ 24    'total days
    
        milliseconds = (timediff - seconds) * 1000  '0-999 ms
        seconds = seconds - minutes * 60    '0-60 seconds
        minutes = minutes - hours * 60      '0-59 minutes
        hours   = hours - days * 24         '0-23 hrs
    
        IF days > 0 THEN
            IF days < 10 THEN
                @a[0] = 32  'CHR$(32) = space
            ELSE
                '@a[0] = FIX(days\10) + 48
                @a[0] = days \ 10 + 48  'CHR$(48) thru CHR$(57) = numbers 0 thru 10
            END IF
            @a[1] = days MOD 10 + 48    'CHR$(48) thru CHR$(57) = numbers 0 thru 10
        ELSE    'equals 0
            @a[0] = 32  'CHR$(32) = space
            @a[1] = 32  'CHR$(32) = space
            @a[2] = 32  'CHR$(32) = space
        END IF
    
        IF hours > 0 THEN
            IF hours < 10 THEN
                IF days > 0 THEN
                    @a[3] = 48  'CHR$(48) = number 0
                ELSE
                    @a[3] = 32  'CHR$(32) = space
                END IF
            ELSE
                '@a[3] = FIX(hours\10) + 48
                @a[3] = hours \ 10 + 48  'CHR$(48) thru CHR$(57) = numbers 0 thru 10
            END IF
            @a[4] = hours MOD 10 + 48    'CHR$(48) thru CHR$(57) = numbers 0 thru 10
        ELSEIF days > 0 THEN
            @a[3] = 48  'CHR$(48) = number 0
            @a[4] = 48  'CHR$(48) = number 0
        ELSE
            @a[3] = 32  'CHR$(32) = space
            @a[4] = 32  'CHR$(32) = space
            @a[5] = 32  'CHR$(32) = space
        END IF
    
        IF minutes > 0 THEN
            IF minutes < 10 THEN
                IF hours > 0 OR days > 0 THEN
                    @a[6] = 48  'CHR$(48) = number 0
                ELSE
                    @a[6] = 32  'CHR$(32) = space
                END IF
            ELSE
                '@a[6] = FIX(minutes\10) + 48
                @a[6] = minutes \ 10 + 48  'CHR$(48) thru CHR$(57) = numbers 0 thru 10
            END IF
            @a[7] = minutes MOD 10 + 48    'CHR$(48) thru CHR$(57) = numbers 0 thru 10
        ELSEIF hours > 0 OR days > 0 THEN
            @a[6] = 48  'CHR$(48) = number 0
            @a[7] = 48  'CHR$(48) = number 0
        ELSE
            @a[6] = 32  'CHR$(32) = space
            @a[7] = 32  'CHR$(32) = space
            @a[8] = 32  'CHR$(32) = space
        END IF
    
        IF seconds > 0 THEN
            IF seconds < 10 THEN
                IF minutes > 0 OR hours > 0 OR days > 0 THEN
                    @a[9] = 48  'CHR$(48) = number 0
                ELSE
                    @a[9] = 32  'CHR$(32) = space
                END IF
            ELSE
                '@a[9] = FIX(seconds\10) + 48
                @a[9] = seconds \ 10 + 48  'CHR$(48) thru CHR$(57) = numbers 0 thru 10
            END IF
            @a[10] = seconds MOD 10 + 48    'CHR$(48) thru CHR$(57) = numbers 0 thru 10
        ELSEIF minutes > 0 OR hours > 0 OR days > 0 THEN
            @a[9]  = 48  'CHR$(48) = number 0
            @a[10] = 48  'CHR$(48) = number 0
        ELSE
            @a[9]  = 32  'CHR$(32) = space
            @a[10] = 32  'CHR$(32) = space
            @a[11] = 32  'CHR$(32) = space
        END IF
    
        IF milliseconds < 100 THEN
            IF seconds > 0 OR minutes > 0 OR hours > 0 OR days > 0 THEN
                @a[12] = 48  'CHR$(48) = number 0
            ELSE
                @a[12] = 32  'CHR$(32) = space
            END IF
            IF milliseconds < 10 THEN
                IF seconds > 0 OR minutes > 0 OR hours > 0 OR days > 0 THEN
                    @a[13] = 48  'CHR$(48) = number 0
                ELSE
                    @a[13] = 32  'CHR$(32) = space
                END IF
                @a[14] = milliseconds + 48  'CHR$(48) thru CHR$(57) = numbers 0 thru 10
            ELSE
                '@a[13] = FIX(milliseconds\10) + 48
                @a[13] = milliseconds \ 10 + 48  'CHR$(48) thru CHR$(57) = numbers 0 thru 10
            END IF
        ELSE
            '@a[12] = FIX(milliseconds\100) + 48 'hundreds
            '@a[13] = FIX((milliseconds - FIX(milliseconds\100) * 100) \ 10) + 48    'tens
            @a[12] = milliseconds \ 100 + 48 'hundreds
            @a[13] = (milliseconds - (milliseconds \ 100) * 100) \ 10 + 48  'CHR$(48) thru CHR$(57) = numbers 0 thru 10    'tens
        END IF
        @a[14] = milliseconds MOD 10 + 48    'CHR$(48) thru CHR$(57) = numbers 0 thru 10    'ones
    
        FUNCTION = sTime
    END FUNCTION
    
    
    
    
    
    
    
    
    
    '============================================================================================================
    '== ShowTaskManDialogProc ===================================================================================
    '============================================================================================================
    CALLBACK FUNCTION ShowTaskManDialogProc()
        STATIC hListview AS DWORD
        LOCAL x, row, col AS LONG
        LOCAL LVI AS LV_ITEM
        LOCAL szBuf AS ASCIIZ * 1000
        LOCAL pNMLV AS NM_LISTVIEW PTR 'for detecting listview double-click (notify)
        LOCAL lpLvCd AS NMLVCUSTOMDRAW PTR  'for custom drawing the listview cells (BG color, etc)
        '-- for custom listview font:
        LOCAL lf  AS LOGFONT
        STATIC hListviewFont AS LONG
        LOCAL hDC AS DWORD
        '-- for process tracking:
        STATIC static_PIDtotrack AS DWORD
        '-- index of column to sort by:
        STATIC static_ColumnToSortBy AS LONG
    
        '-- for calling NtQuerySystemInformation:
        LOCAL NtStatus AS LONG
        LOCAL pSysProcInfo AS SYSTEM_PROCESS_INFORMATION POINTER
        LOCAL SystemInformationLength AS DWORD
        STATIC static_ReturnLength AS DWORD
        LOCAL pBuffer AS DWORD PTR
        LOCAL sBuffer AS STRING
    
        '-- for converting a process's start time into a readable date/time format:
        LOCAL FT, LT, CurrentFT AS FILETIME
        LOCAL ST AS SYSTEMTIME
        LOCAL CurrentTime AS QUAD
    
        '-- a total count of all processes currently running:
        LOCAL ProcessCount AS LONG
        LOCAL CurrentCPUPercent AS SINGLE
    
        '-- used to calculate current CPU usage for each process:
        LOCAL previndex AS LONG
        LOCAL TotalCPUTimeIncreaseOfAllProcesses AS QUAD
        LOCAL CPUpercentage AS SINGLE
    
    
        SELECT CASE AS LONG CBMSG
            CASE %WM_NOTIFY
                SELECT CASE LOWRD(CBWPARAM)
                    CASE %IDC_Listview
                        pNMLV = CBLPARAM
                        SELECT CASE @pNMLV.hdr.code
                            CASE %NM_CLICK  'left click
                                '-- read the PID from the row's lParam (better than reading from the PID column, which could potentially change position):
                                lvi.mask     = %LVIF_PARAM
                                lvi.iItem    = @pNMLV.iItem   '@pNMLV.iItem = row clicked on (0 based)
                                lvi.iSubItem = 0
                                IF SendMessage(hListview, %LVM_GETITEM, 0, VARPTR(lvi)) = %TRUE THEN 'success?
                                    IF static_PIDtotrack = lvi.lParam THEN   'already tracking?
                                        static_PIDtotrack = -1  'stop tracking
                                    ELSE
                                        static_PIDtotrack = lvi.lParam 'track this row/PID
                                    END IF
                                    '-- refresh the listview (InvalidateRect + UpdateWindow doesn't flicker like CONTROL REDRAW does):
                                    'CONTROL REDRAW CBHNDL, %IDC_TopicsListview
                                    InvalidateRect(hListview, BYVAL 0, %FALSE)
                                    UpdateWindow(hListview)
                                END IF
    
                            CASE %NM_RCLICK  'right click
                                'not used in this program (yet)
    
                            CASE %LVN_COLUMNCLICK   'user clicked on a column header
                                ' @pNMLV.iItem    = row clicked on (0 based) ----> THIS WILL ALWAYS BE -1 FOR %LVN_COLUMNCLICK
                                ' @pNMLV.iSubItem = column clicked on (0 based)
                                static_ColumnToSortBy = @pNMLV.isubItem
                                '-- reverse sorting direction:
                                gColumnSortDirection(static_ColumnToSortBy) = NOT gColumnSortDirection(static_ColumnToSortBy)
                                '-- refresh the listview:
                                CALL PostMessage(CBHNDL, %WM_REFRESHLISTVIEW, 0, 0)
    
                            CASE %NM_CUSTOMDRAW     'use custom drawing in the topics listview
                                lpLvCd = CBLPARAM
                                SELECT CASE @lpLvCd.nmcd.dwDrawStage
                                    CASE %CDDS_PREPAINT   'Before the painting cycle begins. (this is the first message received)
                                        FUNCTION = %CDRF_NOTIFYITEMDRAW  'tell Windows we want messages before and after drawing an item. (%CDDS_ITEMPREPAINT OR %CDDS_SUBITEM) and (%CDDS_ITEMPOSTPAINT OR %CDDS_SUBITEM) notifications
    
                                    CASE %CDDS_ITEMPREPAINT  'Before an item is drawn. (this is the second message received)
                                        FUNCTION = %CDRF_NOTIFYSUBITEMDRAW  'tell Windows we want messages before and after drawing a sub-item.
                                        'FUNCTION = %CDRF_NEWFONT   'return this if we changed the font
    
                                    CASE %CDDS_SUBITEM OR %CDDS_ITEMPREPAINT    'this message indicates that drawing is about to occur for a subitem (an individual cell in the listview)
                                        'This notification is received only if the listview is in report mode and it returned %CDRF_NOTIFYSUBITEMREDRAW for %CDDS_ITEMPREPAINT
                                        ' @lpLvCd.iSubItem        = column (0 based)
                                        ' @lpLvCd.nmcd.dwItemSpec = row    (0 based)
    
                                        'IF static_PIDtotrack = gProcess_ID(@lpLvCd.nmcd.dwItemSpec) THEN
                                        IF static_PIDtotrack = gProcess_ID(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) THEN
                                            '-- highlight the process that's being tracked:
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = %YELLOW
                                        'ELSEIF @lpLvCd.iSubItem = 0 THEN    'is this cell in the first column? (iSubItem is the column's position)
                                        '    @lpLvCd.clrText   = %BLACK
                                        '    @lpLvCd.clrTextBk = RGB(158,190,189)
                                        ELSE    'a cell NOT in the first column and NOT in a row containing a process being watched
                                            IF (@lpLvCd.nmcd.dwItemSpec MOD 2) = 0 THEN 'odd row
                                                @lpLvCd.clrText   = %BLACK
                                                @lpLvCd.clrTextBk = %WHITE
                                            ELSE    'even row
                                                @lpLvCd.clrText   = %BLACK
                                                @lpLvCd.clrTextBk = RGB(197,235,245)  'RGB(197,216,215)
                                            END IF
                                        END IF
    
                                        '-- indicate delta changes (over-ruling the selected color):
                                        IF @lpLvCd.iSubItem = %ColumnNumber_UserTime AND gProcess_UserTime_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_UserTime_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_KernelTime AND gProcess_KernelTime_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_KernelTime_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_TotalCPUTime AND gProcess_TotalCPUTime_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_TotalCPUTime_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_CPUPercent AND gProcess_CPUPercent(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_CPUPercent_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_BasePriority AND gProcess_BasePriority_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_BasePriority_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_HandleCount AND gProcess_HandleCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_HandleCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_NumberOfThreads AND gProcess_NumberOfThreads_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_NumberOfThreads_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_PeakVirtualSize AND gProcess_PeakVirtualSize_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_PeakVirtualSize_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_VirtualSize AND gProcess_VirtualSize_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_VirtualSize(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_PageFaultCount AND gProcess_PageFaultCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_PageFaultCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_PeakWorkingSetSize AND gProcess_PeakWorkingSetSize_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_PeakWorkingSetSize_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_WorkingSetSize AND gProcess_WorkingSetSize_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_WorkingSetSize_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_PrivatePageCount AND gProcess_PrivatePageCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_PrivatePageCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_ReadOperationCount AND gProcess_ReadOperationCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_ReadOperationCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_WriteOperationCount AND gProcess_WriteOperationCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_WriteOperationCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_OtherOperationCount AND gProcess_OtherOperationCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_OtherOperationCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_ReadTransferCount AND gProcess_ReadTransferCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_ReadTransferCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_WriteTransferCount AND gProcess_WriteTransferCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_WriteTransferCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        ELSEIF @lpLvCd.iSubItem = %ColumnNumber_OtherTransferCount AND gProcess_OtherTransferCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) <> 0 THEN
                                            @lpLvCd.clrText   = %BLACK
                                            @lpLvCd.clrTextBk = IIF&(gProcess_OtherTransferCount_Delta(gSortedIndex(@lpLvCd.nmcd.dwItemSpec)) > 0, %CYAN, %GREEN)
                                        END IF
    
                                        '-- change the font (NOTE - the row height will not match the font size unless %WM_SETFONT was called during %WM_INITDIALOG):
                                        IF hListviewFont THEN SelectObject(@lpLvCd.nmcd.hDC, hListviewFont) 'hListviewFont should be STATIC (initialized during %WM_INITDIALOG)
    
                                        FUNCTION = %CDRF_NEWFONT  'MS says to return %CDRF_NEWFONT if the color or font was changed
    
                                END SELECT  '@lpLvCd.nmcd.dwDrawStage - current drawing stage for %NM_CUSTOMDRAW
    
                        END SELECT  '@pNMLV.hdr.code - message sent to the control
    
                END SELECT  'LOWRD(CBWPARAM) - control ID
    
            '------------------------------------------------------------------------------------------------------
    
            CASE %WM_TIMER
                IF CBWPARAM = %TIMERID THEN PostMessage(CBHNDL, %WM_REFRESHLISTVIEW, 0, 0)
    
            '------------------------------------------------------------------------------------------------------
    
            CASE %WM_REFRESHLISTVIEW  'this message is sent from %WM_INITDIALOG and %WM_TIMER, so the listview can refresh at startup or during the timer
    
                DO
                    ' the very first call to NtQuerySystemInformation can have SystemInformationLength set to 0.
                    ' when the buffer is too small, %STATUS_INFO_LENGTH_MISMATCH will be returned along with the required buffer size (static_ReturnLength).
                    ' we can then call NtQuerySystemInformation again with a bigger buffer size. the result should then be %STATUS_SUCCESS.
                    ' static_ReturnLength is STATIC so it will be remembered inbetween updates.
                    ' %STATUS_INFO_LENGTH_MISMATCH should only occur during startup, and when the total process count reaches a new maximum.
                    SystemInformationLength = static_ReturnLength      '<-- should one be added to the static_ReturnLength to account for an ending $NUL? don't know. but for now, don't worry about it :)
                    sBuffer = NUL$(static_ReturnLength)
                    pBuffer = STRPTR(sBuffer)
                    NtStatus = NtQuerySystemInformation(%SystemProcessesAndThreadsInformation, pBuffer, SystemInformationLength, VARPTR(static_ReturnLength))
                LOOP WHILE NtStatus = %STATUS_INFO_LENGTH_MISMATCH  'it should never need to loop more than once
    
    
                '-- if NtStatus is not %STATUS_SUCCESS, then something is wrong, so we'll just abort:
                IF NtStatus <> %STATUS_SUCCESS THEN
                    MSGBOX "NtQuerySystemInformation failed.", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
                    EXIT FUNCTION
                END IF
    
                '-- at this point, we can assume that we have successfully retrieved the information.
                ' now set the struct's pointer to point to the returned buffer.
                ' in a sense, the struct acts as a template which is moved along the buffer as directed by @pSysProcInfo.NextEntryOffset, allowing us to look at the data for each process.
                pSysProcInfo = pBuffer
    
                '-- get the current time (as FILETIME): it'll be used to calculate how long a process has been running (optional column; not important at all)
                GetSystemTimeAsFileTime(CurrentFT)
                CurrentTime = MAK(QUAD, CurrentFT.dwLowDateTime, CurrentFT.dwHighDateTime)
    
                '-- reset these. they'll be recalculated during the loop:
                RESET ProcessCount, TotalCPUTimeIncreaseOfAllProcesses
    
                '-- loop through all the processes:
                DO
                    '-- increase the size of the arrays as needed (note that they are zero based, and they never need to shrink):
                    IF ProcessCount > UBOUND(gProcess_Index) THEN
                        REDIM PRESERVE gProcess_Index(ProcessCount) AS LONG
                        REDIM PRESERVE gSortedIndex(ProcessCount)   AS LONG
    
                        REDIM PRESERVE gProcess_NumberOfThreads(ProcessCount)            AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_CreateTime(ProcessCount)                 AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_UserTime(ProcessCount)                   AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_KernelTime(ProcessCount)                 AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_TotalCPUTime(ProcessCount)               AS GLOBAL QUAD    'calculated value
                        REDIM PRESERVE gProcess_RunningTime(ProcessCount)                AS GLOBAL QUAD    'calculated value
                        REDIM PRESERVE gProcess_CPUPercent(ProcessCount)                 AS GLOBAL SINGLE  'calculated value
                        REDIM PRESERVE gProcess_Name(ProcessCount)                       AS GLOBAL STRING
                        REDIM PRESERVE gProcess_BasePriority(ProcessCount)               AS GLOBAL LONG
                        REDIM PRESERVE gProcess_ID(ProcessCount)                         AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_ParentProcessID(ProcessCount)            AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_HandleCount(ProcessCount)                AS GLOBAL DWORD
                        '-- memory data:
                        REDIM PRESERVE gProcess_PeakVirtualSize(ProcessCount)            AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_VirtualSize(ProcessCount)                AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_PageFaultCount(ProcessCount)             AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_PeakWorkingSetSize(ProcessCount)         AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_WorkingSetSize(ProcessCount)             AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_QuotaPeakPagedPoolUsage(ProcessCount)    AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_QuotaPagedPoolUsage(ProcessCount)        AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_QuotaPeakNonPagedPoolUsage(ProcessCount) AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_QuotaNonPagedPoolUsage(ProcessCount)     AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_PagefileUsage(ProcessCount)              AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_PeakPagefileUsage(ProcessCount)          AS GLOBAL DWORD
                        REDIM PRESERVE gProcess_PrivatePageCount(ProcessCount)           AS GLOBAL DWORD
                        '-- I/O data:
                        REDIM PRESERVE gProcess_ReadOperationCount(ProcessCount)         AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_WriteOperationCount(ProcessCount)        AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_OtherOperationCount(ProcessCount)        AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_ReadTransferCount(ProcessCount)          AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_WriteTransferCount(ProcessCount)         AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_OtherTransferCount(ProcessCount)         AS GLOBAL QUAD
    
                        '== deltas (optional; these are used to indicate changes in the listview) ========================================
                        REDIM PRESERVE gProcess_NumberOfThreads_Delta(ProcessCount)            AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_UserTime_Delta(ProcessCount)                   AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_KernelTime_Delta(ProcessCount)                 AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_TotalCPUTime_Delta(ProcessCount)               AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_CPUPercent_Delta(ProcessCount)                 AS GLOBAL SINGLE
                        REDIM PRESERVE gProcess_BasePriority_Delta(ProcessCount)               AS GLOBAL LONG
                        REDIM PRESERVE gProcess_HandleCount_Delta(ProcessCount)                AS GLOBAL LONG 'DWORD
                        '-- memory data:
                        REDIM PRESERVE gProcess_PeakVirtualSize_Delta(ProcessCount)            AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_VirtualSize_Delta(ProcessCount)                AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_PageFaultCount_Delta(ProcessCount)             AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_PeakWorkingSetSize_Delta(ProcessCount)         AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_WorkingSetSize_Delta(ProcessCount)             AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_QuotaPeakPagedPoolUsage_Delta(ProcessCount)    AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_QuotaPagedPoolUsage_Delta(ProcessCount)        AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_QuotaPeakNonPagedPoolUsage_Delta(ProcessCount) AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_QuotaNonPagedPoolUsage_Delta(ProcessCount)     AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_PagefileUsage_Delta(ProcessCount)              AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_PeakPagefileUsage_Delta(ProcessCount)          AS GLOBAL LONG 'DWORD
                        REDIM PRESERVE gProcess_PrivatePageCount_Delta(ProcessCount)           AS GLOBAL LONG 'DWORD
                        '-- I/O data:
                        REDIM PRESERVE gProcess_ReadOperationCount_Delta(ProcessCount)         AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_WriteOperationCount_Delta(ProcessCount)        AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_OtherOperationCount_Delta(ProcessCount)        AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_ReadTransferCount_Delta(ProcessCount)          AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_WriteTransferCount_Delta(ProcessCount)         AS GLOBAL QUAD
                        REDIM PRESERVE gProcess_OtherTransferCount_Delta(ProcessCount)         AS GLOBAL QUAD
    
                        '== backup copies of arrays that may have a delta ==================================================================
                        'NOTE: the PID will never have a delta, but it's required for obtaining the index to all the other PREVIOUS_ arrays
                        '      the CreateTime won't have a delta either, but it's used as a safety measure for verifying the correct process (PIDs are recycled by Windows)
                        REDIM PRESERVE PREVIOUS_NumberOfThreads(ProcessCount)            AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_CreateTime(ProcessCount)                 AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_UserTime(ProcessCount)                   AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_KernelTime(ProcessCount)                 AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_TotalCPUTime(ProcessCount)               AS STATIC QUAD    'calculated value
                        REDIM PRESERVE PREVIOUS_RunningTime(ProcessCount)                AS STATIC QUAD    'calculated value
                        REDIM PRESERVE PREVIOUS_CPUPercent(ProcessCount)                 AS STATIC SINGLE  'calculated value
                        REDIM PRESERVE PREVIOUS_BasePriority(ProcessCount)               AS STATIC LONG
                        REDIM PRESERVE PREVIOUS_ID(ProcessCount)                         AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_HandleCount(ProcessCount)                AS STATIC DWORD
                        '-- memory data:
                        REDIM PRESERVE PREVIOUS_PeakVirtualSize(ProcessCount)            AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_VirtualSize(ProcessCount)                AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_PageFaultCount(ProcessCount)             AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_PeakWorkingSetSize(ProcessCount)         AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_WorkingSetSize(ProcessCount)             AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_QuotaPeakPagedPoolUsage(ProcessCount)    AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_QuotaPagedPoolUsage(ProcessCount)        AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_QuotaPeakNonPagedPoolUsage(ProcessCount) AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_QuotaNonPagedPoolUsage(ProcessCount)     AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_PagefileUsage(ProcessCount)              AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_PeakPagefileUsage(ProcessCount)          AS STATIC DWORD
                        REDIM PRESERVE PREVIOUS_PrivatePageCount(ProcessCount)           AS STATIC DWORD
                        '-- I/O data:
                        REDIM PRESERVE PREVIOUS_ReadOperationCount(ProcessCount)         AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_WriteOperationCount(ProcessCount)        AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_OtherOperationCount(ProcessCount)        AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_ReadTransferCount(ProcessCount)          AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_WriteTransferCount(ProcessCount)         AS STATIC QUAD
                        REDIM PRESERVE PREVIOUS_OtherTransferCount(ProcessCount)         AS STATIC QUAD
                    END IF
    
                    'gProcess_Index() is used for sorting all the arrays:
                    gProcess_Index(ProcessCount) = ProcessCount
    
                    gProcess_ID(ProcessCount)                         = @pSysProcInfo.ProcessID
                    gProcess_ParentProcessID(ProcessCount)            = @pSysProcInfo.ParentProcessID
                    gProcess_NumberOfThreads(ProcessCount)            = @pSysProcInfo.NumberOfThreads
                    gProcess_CreateTime(ProcessCount)                 = @pSysProcInfo.CreateTime
                    gProcess_UserTime(ProcessCount)                   = @pSysProcInfo.UserTime
                    gProcess_KernelTime(ProcessCount)                 = @pSysProcInfo.KernelTime
                    gProcess_TotalCPUTime(ProcessCount)               = @pSysProcInfo.UserTime + @pSysProcInfo.KernelTime
                    gProcess_BasePriority(ProcessCount)               = @pSysProcInfo.BasePriority
                    gProcess_HandleCount(ProcessCount)                = @pSysProcInfo.HandleCount
                    '-- memory data:
                    gProcess_PeakVirtualSize(ProcessCount)            = @pSysProcInfo.VMCounters.PeakVirtualSize
                    gProcess_VirtualSize(ProcessCount)                = @pSysProcInfo.VMCounters.VirtualSize
                    gProcess_PageFaultCount(ProcessCount)             = @pSysProcInfo.VMCounters.PageFaultCount
                    gProcess_PeakWorkingSetSize(ProcessCount)         = @pSysProcInfo.VMCounters.PeakWorkingSetSize
                    gProcess_WorkingSetSize(ProcessCount)             = @pSysProcInfo.VMCounters.WorkingSetSize
                    gProcess_QuotaPeakPagedPoolUsage(ProcessCount)    = @pSysProcInfo.VMCounters.QuotaPeakPagedPoolUsage
                    gProcess_QuotaPagedPoolUsage(ProcessCount)        = @pSysProcInfo.VMCounters.QuotaPagedPoolUsage
                    gProcess_QuotaPeakNonPagedPoolUsage(ProcessCount) = @pSysProcInfo.VMCounters.QuotaPeakNonPagedPoolUsage
                    gProcess_QuotaNonPagedPoolUsage(ProcessCount)     = @pSysProcInfo.VMCounters.QuotaNonPagedPoolUsage
                    gProcess_PagefileUsage(ProcessCount)              = @pSysProcInfo.VMCounters.PagefileUsage
                    gProcess_PeakPagefileUsage(ProcessCount)          = @pSysProcInfo.VMCounters.PeakPagefileUsage
                    gProcess_PrivatePageCount(ProcessCount)           = @pSysProcInfo.VMCounters.PrivatePageCount
                    '-- I/O data:
                    gProcess_ReadOperationCount(ProcessCount)         = @pSysProcInfo.IOCounters.ReadOperationCount
                    gProcess_WriteOperationCount(ProcessCount)        = @pSysProcInfo.IOCounters.WriteOperationCount
                    gProcess_OtherOperationCount(ProcessCount)        = @pSysProcInfo.IOCounters.OtherOperationCount
                    gProcess_ReadTransferCount(ProcessCount)          = @pSysProcInfo.IOCounters.ReadTransferCount
                    gProcess_WriteTransferCount(ProcessCount)         = @pSysProcInfo.IOCounters.WriteTransferCount
                    gProcess_OtherTransferCount(ProcessCount)         = @pSysProcInfo.IOCounters.OtherTransferCount
    
                    '-- calculate how long the process has been running:
                    IF @pSysProcInfo.CreateTime > 0 THEN    'does this process have a CreateTime? (nearly all of them should)
                        gProcess_RunningTime(ProcessCount) = CurrentTime - @pSysProcInfo.CreateTime
                    ELSE 'System Idle Process and System will not have a valid CreateTime
                        gProcess_RunningTime(ProcessCount) = 0      'instead of 0, it could potentially be set to the value of CurrentTime - SystemUpTime (would need to retrieve the uptime though)
                    END IF
    
                    '-- if the process ID is not 0 then use WStrToAnsi (or ACODE$ and PEEK$) to convert the provided name from Unicode to ANSI..
                    IF @pSysProcInfo.ProcessID > 0 THEN
                        gProcess_Name(ProcessCount) = ACODE$(PEEK$(@pSysProcInfo.ProcessName.pBuffer, @pSysProcInfo.ProcessName.Length))
                    ELSE  'the idle process ID is 0, and won't have a name, so give it one..
                        gProcess_Name(ProcessCount) = "System Idle Process"
                    END IF
    
    
    
                    '-- search for a previous array index for this process:
                    ' it will usually be the same index as ProcessCount, but when processes are opened/closed sometimes the data will get auto of sync between refreshes
                    ' using the PREVIOUS_ arrays guarantees that all the data matches up correctly for each process. otherwise, we'd sometimes end up with weird numbers, such as negative CPU times, etc..
                    ARRAY SCAN PREVIOUS_ID(), = @pSysProcInfo.ProcessID, TO previndex  'this returns an index relative to the LBOUND of the array
                    IF previndex > 0 AND PREVIOUS_CreateTime(previndex - 1) = @pSysProcInfo.CreateTime THEN   'was the ProcessID found, and does the creation time match across both refreshes? (PIDs get recycled)
                        DECR previndex 'subtract 1 from the previndex because the arrays are 0 based, and ARRAY SCAN returned a 1 based result
    
                        '-- calculate the deltas for each array that may have one (ProcessID, ProcessName, etc will never have a delta):
                        gProcess_NumberOfThreads_Delta(ProcessCount)            = @pSysProcInfo.NumberOfThreads                       - PREVIOUS_NumberOfThreads(previndex)
                        gProcess_UserTime_Delta(ProcessCount)                   = @pSysProcInfo.UserTime                              - PREVIOUS_UserTime(previndex)
                        gProcess_KernelTime_Delta(ProcessCount)                 = @pSysProcInfo.KernelTime                            - PREVIOUS_KernelTime(previndex)
                        gProcess_TotalCPUTime_Delta(ProcessCount)               = @pSysProcInfo.UserTime + @pSysProcInfo.KernelTime   - PREVIOUS_TotalCPUTime(previndex)
                        gProcess_BasePriority_Delta(ProcessCount)               = @pSysProcInfo.BasePriority                          - PREVIOUS_BasePriority(previndex)
                        gProcess_HandleCount_Delta(ProcessCount)                = @pSysProcInfo.HandleCount                           - PREVIOUS_HandleCount(previndex)
                        '-- memory data:
                        gProcess_PeakVirtualSize_Delta(ProcessCount)            = @pSysProcInfo.VMCounters.PeakVirtualSize            - PREVIOUS_PeakVirtualSize(previndex)
                        gProcess_VirtualSize_Delta(ProcessCount)                = @pSysProcInfo.VMCounters.VirtualSize                - PREVIOUS_VirtualSize(previndex)
                        gProcess_PageFaultCount_Delta(ProcessCount)             = @pSysProcInfo.VMCounters.PageFaultCount             - PREVIOUS_PageFaultCount(previndex)
                        gProcess_PeakWorkingSetSize_Delta(ProcessCount)         = @pSysProcInfo.VMCounters.PeakWorkingSetSize         - PREVIOUS_PeakWorkingSetSize(previndex)
                        gProcess_WorkingSetSize_Delta(ProcessCount)             = @pSysProcInfo.VMCounters.WorkingSetSize             - PREVIOUS_WorkingSetSize(previndex)
                        gProcess_QuotaPeakPagedPoolUsage_Delta(ProcessCount)    = @pSysProcInfo.VMCounters.QuotaPeakPagedPoolUsage    - PREVIOUS_QuotaPeakPagedPoolUsage(previndex)
                        gProcess_QuotaPagedPoolUsage_Delta(ProcessCount)        = @pSysProcInfo.VMCounters.QuotaPagedPoolUsage        - PREVIOUS_QuotaPagedPoolUsage(previndex)
                        gProcess_QuotaPeakNonPagedPoolUsage_Delta(ProcessCount) = @pSysProcInfo.VMCounters.QuotaPeakNonPagedPoolUsage - PREVIOUS_QuotaPeakNonPagedPoolUsage(previndex)
                        gProcess_QuotaNonPagedPoolUsage_Delta(ProcessCount)     = @pSysProcInfo.VMCounters.QuotaNonPagedPoolUsage     - PREVIOUS_QuotaNonPagedPoolUsage(previndex)
                        gProcess_PagefileUsage_Delta(ProcessCount)              = @pSysProcInfo.VMCounters.PagefileUsage              - PREVIOUS_PagefileUsage(previndex)
                        gProcess_PeakPagefileUsage_Delta(ProcessCount)          = @pSysProcInfo.VMCounters.PeakPagefileUsage          - PREVIOUS_PeakPagefileUsage(previndex)
                        gProcess_PrivatePageCount_Delta(ProcessCount)           = @pSysProcInfo.VMCounters.PrivatePageCount           - PREVIOUS_PrivatePageCount(previndex)
                        '-- I/O data:
                        gProcess_ReadOperationCount_Delta(ProcessCount)         = @pSysProcInfo.IOCounters.ReadOperationCount         - PREVIOUS_ReadOperationCount(previndex)
                        gProcess_WriteOperationCount_Delta(ProcessCount)        = @pSysProcInfo.IOCounters.WriteOperationCount        - PREVIOUS_WriteOperationCount(previndex)
                        gProcess_OtherOperationCount_Delta(ProcessCount)        = @pSysProcInfo.IOCounters.OtherOperationCount        - PREVIOUS_OtherOperationCount(previndex)
                        gProcess_ReadTransferCount_Delta(ProcessCount)          = @pSysProcInfo.IOCounters.ReadTransferCount          - PREVIOUS_ReadTransferCount(previndex)
                        gProcess_WriteTransferCount_Delta(ProcessCount)         = @pSysProcInfo.IOCounters.WriteTransferCount         - PREVIOUS_WriteTransferCount(previndex)
                        gProcess_OtherTransferCount_Delta(ProcessCount)         = @pSysProcInfo.IOCounters.OtherTransferCount         - PREVIOUS_OtherTransferCount(previndex)
    
                    ELSE    'if the ProcessID was NOT found, then the process may have just started running and there is no previous data for it yet
                        gProcess_NumberOfThreads_Delta(ProcessCount)            = @pSysProcInfo.NumberOfThreads
                        gProcess_UserTime_Delta(ProcessCount)                   = @pSysProcInfo.UserTime
                        gProcess_KernelTime_Delta(ProcessCount)                 = @pSysProcInfo.KernelTime
                        gProcess_TotalCPUTime_Delta(ProcessCount)               = @pSysProcInfo.UserTime + @pSysProcInfo.KernelTime
                        gProcess_BasePriority_Delta(ProcessCount)               = @pSysProcInfo.BasePriority
                        gProcess_HandleCount_Delta(ProcessCount)                = @pSysProcInfo.HandleCount
                        '-- memory data:
                        gProcess_PeakVirtualSize_Delta(ProcessCount)            = @pSysProcInfo.VMCounters.PeakVirtualSize
                        gProcess_VirtualSize_Delta(ProcessCount)                = @pSysProcInfo.VMCounters.VirtualSize
                        gProcess_PageFaultCount_Delta(ProcessCount)             = @pSysProcInfo.VMCounters.PageFaultCount
                        gProcess_PeakWorkingSetSize_Delta(ProcessCount)         = @pSysProcInfo.VMCounters.PeakWorkingSetSize
                        gProcess_WorkingSetSize_Delta(ProcessCount)             = @pSysProcInfo.VMCounters.WorkingSetSize
                        gProcess_QuotaPeakPagedPoolUsage_Delta(ProcessCount)    = @pSysProcInfo.VMCounters.QuotaPeakPagedPoolUsage
                        gProcess_QuotaPagedPoolUsage_Delta(ProcessCount)        = @pSysProcInfo.VMCounters.QuotaPagedPoolUsage
                        gProcess_QuotaPeakNonPagedPoolUsage_Delta(ProcessCount) = @pSysProcInfo.VMCounters.QuotaPeakNonPagedPoolUsage
                        gProcess_QuotaNonPagedPoolUsage_Delta(ProcessCount)     = @pSysProcInfo.VMCounters.QuotaNonPagedPoolUsage
                        gProcess_PagefileUsage_Delta(ProcessCount)              = @pSysProcInfo.VMCounters.PagefileUsage
                        gProcess_PeakPagefileUsage_Delta(ProcessCount)          = @pSysProcInfo.VMCounters.PeakPagefileUsage
                        gProcess_PrivatePageCount_Delta(ProcessCount)           = @pSysProcInfo.VMCounters.PrivatePageCount
                        '-- I/O data:
                        gProcess_ReadOperationCount_Delta(ProcessCount)         = @pSysProcInfo.IOCounters.ReadOperationCount
                        gProcess_WriteOperationCount_Delta(ProcessCount)        = @pSysProcInfo.IOCounters.WriteOperationCount
                        gProcess_OtherOperationCount_Delta(ProcessCount)        = @pSysProcInfo.IOCounters.OtherOperationCount
                        gProcess_ReadTransferCount_Delta(ProcessCount)          = @pSysProcInfo.IOCounters.ReadTransferCount
                        gProcess_WriteTransferCount_Delta(ProcessCount)         = @pSysProcInfo.IOCounters.WriteTransferCount
                        gProcess_OtherTransferCount_Delta(ProcessCount)         = @pSysProcInfo.IOCounters.OtherTransferCount
                    END IF
    
    
                    '-- keep a running total (for this refresh cycle) of the CPU time deltas for all processes:
                    ' this value is used to calculate CPU percentages.
                    TotalCPUTimeIncreaseOfAllProcesses = TotalCPUTimeIncreaseOfAllProcesses + gProcess_TotalCPUTime_Delta(ProcessCount)
    
    
    
                    '-- exit when there's no more process entries:
                    IF @pSysProcInfo.NextEntryOffset = 0 THEN
                        EXIT LOOP
                    ELSE    'otherwise, increment ProcessCount, and advance to the data struct of the next process:
                        INCR ProcessCount
                        pSysProcInfo = pSysProcInfo + @pSysProcInfo.NextEntryOffset
                    END IF
    
                LOOP  'loop through all the process data returned by NtQuerySystemInformation
    
    
    
    
                '-- now that all the deltas have been calculated in the previous loop, we can make backup copies of the important data
                ' this is so each process's data can be identified during the next refresh, and delta values can be calculated
                ' and here, we can now use TotalCPUTimeIncreaseOfAllProcesses to calculate CPU percentages
                FOR x = 0 TO ProcessCount
                    'NOTE: the PID and CreateTime won't have deltas, but they are required to search for processes
                    PREVIOUS_ID(x)                         = gProcess_ID(x)
                    PREVIOUS_NumberOfThreads(x)            = gProcess_NumberOfThreads(x)
                    PREVIOUS_CreateTime(x)                 = gProcess_CreateTime(x)
                    PREVIOUS_UserTime(x)                   = gProcess_UserTime(x)
                    PREVIOUS_KernelTime(x)                 = gProcess_KernelTime(x)
                    PREVIOUS_TotalCPUTime(x)               = gProcess_TotalCPUTime(x)
                    PREVIOUS_BasePriority(x)               = gProcess_BasePriority(x)
                    PREVIOUS_HandleCount(x)                = gProcess_HandleCount(x)
                    '-- memory data:
                    PREVIOUS_PeakVirtualSize(x)            = gProcess_PeakVirtualSize(x)
                    PREVIOUS_VirtualSize(x)                = gProcess_VirtualSize(x)
                    PREVIOUS_PageFaultCount(x)             = gProcess_PageFaultCount(x)
                    PREVIOUS_PeakWorkingSetSize(x)         = gProcess_PeakWorkingSetSize(x)
                    PREVIOUS_WorkingSetSize(x)             = gProcess_WorkingSetSize(x)
                    PREVIOUS_QuotaPeakPagedPoolUsage(x)    = gProcess_QuotaPeakPagedPoolUsage(x)
                    PREVIOUS_QuotaPagedPoolUsage(x)        = gProcess_QuotaPagedPoolUsage(x)
                    PREVIOUS_QuotaPeakNonPagedPoolUsage(x) = gProcess_QuotaPeakNonPagedPoolUsage(x)
                    PREVIOUS_QuotaNonPagedPoolUsage(x)     = gProcess_QuotaNonPagedPoolUsage(x)
                    PREVIOUS_PagefileUsage(x)              = gProcess_PagefileUsage(x)
                    PREVIOUS_PeakPagefileUsage(x)          = gProcess_PeakPagefileUsage(x)
                    PREVIOUS_PrivatePageCount(x)           = gProcess_PrivatePageCount(x)
                    '-- I/O data:
                    PREVIOUS_ReadOperationCount(x)         = gProcess_ReadOperationCount(x)
                    PREVIOUS_WriteOperationCount(x)        = gProcess_WriteOperationCount(x)
                    PREVIOUS_OtherOperationCount(x)        = gProcess_OtherOperationCount(x)
                    PREVIOUS_ReadTransferCount(x)          = gProcess_ReadTransferCount(x)
                    PREVIOUS_WriteTransferCount(x)         = gProcess_WriteTransferCount(x)
                    PREVIOUS_OtherTransferCount(x)         = gProcess_OtherTransferCount(x)
    
                    '-- calculate the current CPU usage for each process:
                    ' CPU % = (the CPU time increase of this process since the last refresh) divided by (the total CPU time increase from all processes since the last refresh) multipied by 100
                    CPUpercentage = gProcess_TotalCPUTime_Delta(x) / TotalCPUTimeIncreaseOfAllProcesses * 100
                    ' gProcess_CPUPercent_Delta(x) = the change of CPU % for this process. could be positive or negative.
                    gProcess_CPUPercent_Delta(x) = CPUpercentage - gProcess_CPUPercent(x)
                    gProcess_CPUPercent(x) = CPUpercentage
                    '-- if this is the System Idle Process (PID=0) then use it to find the system's current CPU usage:
                    IF gProcess_ID(x) = 0 THEN CurrentCPUPercent = 100 - gProcess_CPUPercent(x)
                NEXT
    
    
    
    
    
    
    
    
                '-- sort one of the arrays (gProcess_Index will be used later to sort the other arrays, keeping the data lined up correctly):
                ' remember that the sorted array should NOT use gProcess_Index! it should use the regular index instead, because it's already sorted.
                ' actually, that's not quite true since gSortedIndex is being used now..
                IF static_ColumnToSortBy = %ColumnNumber_Name THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_Name() FOR ProcessCount + 1, COLLATE UCASE, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_Name() FOR ProcessCount + 1, COLLATE UCASE, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ID THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_ID() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_ID() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ParentProcessID THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_ParentProcessID() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_ParentProcessID() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_CreateTime THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_CreateTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_CreateTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_RunningTime THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_RunningTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_RunningTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_UserTime THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_UserTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_UserTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_KernelTime THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_KernelTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_KernelTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_TotalCPUTime THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_TotalCPUTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_TotalCPUTime() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_CPUPercent THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_CPUPercent() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_CPUPercent() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_BasePriority THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_BasePriority() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_BasePriority() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_HandleCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_HandleCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_HandleCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_NumberOfThreads THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_NumberOfThreads() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_NumberOfThreads() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PeakVirtualSize THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_PeakVirtualSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_PeakVirtualSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_VirtualSize THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_VirtualSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_VirtualSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PageFaultCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_PageFaultCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_PageFaultCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PeakWorkingSetSize THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_PeakWorkingSetSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_PeakWorkingSetSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_WorkingSetSize THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_WorkingSetSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_WorkingSetSize() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PrivatePageCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_PrivatePageCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_PrivatePageCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ReadOperationCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_ReadOperationCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_ReadOperationCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_WriteOperationCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_WriteOperationCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_WriteOperationCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_OtherOperationCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_OtherOperationCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_OtherOperationCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ReadTransferCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_ReadTransferCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_ReadTransferCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_WriteTransferCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_WriteTransferCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_WriteTransferCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_OtherTransferCount THEN
                    IF gColumnSortDirection(static_ColumnToSortBy) =  0 THEN ARRAY SORT gProcess_OtherTransferCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), ASCEND
                    IF gColumnSortDirection(static_ColumnToSortBy) = -1 THEN ARRAY SORT gProcess_OtherTransferCount() FOR ProcessCount + 1, TAGARRAY gProcess_Index(), DESCEND
                END IF
    
                '-- make a copy of the sorted index, so the array that was sorted can be UNsorted (makes things easier when filling the listview):
                RESET gSortedIndex()  'maybe not necessary to reset, but do it anyway
                FOR x = 0 TO ProcessCount
                    gSortedIndex(x) = gProcess_Index(x)
                NEXT
    
                '-- UNsort the array that was sorted:
                IF static_ColumnToSortBy = %ColumnNumber_Name THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_Name(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ID THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_ID(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ParentProcessID THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_ParentProcessID(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_CreateTime THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_CreateTime(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_RunningTime THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_RunningTime(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_UserTime THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_UserTime(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_KernelTime THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_KernelTime(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_TotalCPUTime THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_TotalCPUTime(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_CPUPercent THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_CPUPercent(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_BasePriority THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_BasePriority(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_HandleCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_HandleCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_NumberOfThreads THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_NumberOfThreads(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PeakVirtualSize THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_PeakVirtualSize(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_VirtualSize THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_VirtualSize(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PageFaultCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_PageFaultCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PeakWorkingSetSize THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_PeakWorkingSetSize(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_WorkingSetSize THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_WorkingSetSize(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_PrivatePageCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_PrivatePageCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ReadOperationCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_ReadOperationCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_WriteOperationCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_WriteOperationCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_OtherOperationCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_OtherOperationCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_ReadTransferCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_ReadTransferCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_WriteTransferCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_WriteTransferCount(), ASCEND
                ELSEIF static_ColumnToSortBy = %ColumnNumber_OtherTransferCount THEN
                    ARRAY SORT gProcess_Index() FOR ProcessCount + 1, TAGARRAY gProcess_OtherTransferCount(), ASCEND
                END IF
    
    
    
                '-- no need to waste time updating the window if it's not visible:
                'if IsWindowVisible(CBHNDL) = %FALSE then exit function
    
                '-- add one to the ProcessCount to go from 0 to 1 based:
                DIALOG SET TEXT CBHNDL, $Program & " - " & FORMAT$(ProcessCount + 1) & " processes - CPU " & FORMAT$(ROUND(CurrentCPUPercent, 2)) & "%" '& _
    
                '-- temporarily prevent the listview from redrawing itself (reduces flicker while deleting/adding items):
                SendMessage(hListview, %WM_SETREDRAW, %FALSE, 0)
    
                '-- remember the current position of the scrollbars:
                LOCAL LvSbPosX AS LONG
                LOCAL LvSbPosY AS LONG
                LOCAL LvRcSize AS RECT
                SendMessage(hListView, %LVM_GETITEMRECT, BYVAL %NULL, BYVAL VARPTR(LvRcSize))
                LvSbPosX = GetScrollPos(hListView, %SB_HORZ) 'Get horizontal scrollbar position
                LvSbPosY = GetScrollPos(hListView, %SB_VERT) * (LvRcSize.nBottom - LvRcSize.nTop) 'Get vertical scrollbar position
    
                '-- remember how many rows there are before using %LVM_DELETEALLITEMS:
                LOCAL PreviousNumberOfRowsInListview AS LONG
                PreviousNumberOfRowsInListview = SendMessage(hListview, %LVM_GETITEMCOUNT, 0, 0)
                DECR PreviousNumberOfRowsInListview     'subtract one to convert from 1 based to 0 based (ProcessCount is also 0 based)
    
                '-- clear the listview (necessary to remove processes from the end rows, when the process count decreases):
                SendMessage(hListview, %LVM_DELETEALLITEMS, 0, 0)
    
                '-- set the item count (the number of items that the list-view control will ultimately contain):
                ' this message is NOT required but it can potentially make adding a lot of items go faster. I don't see any difference.
                SendMessage(hListview, %LVM_SETITEMCOUNT, ProcessCount + 1, %LVSICF_NOINVALIDATEALL OR %LVSICF_NOSCROLL)   '%LVSICF_NOSCROLL doesn't seem to help
    
                '-- fill the listview:
                FOR row = 0 TO ProcessCount
                    LVI.iItem      = row '0 based
                    LVI.pszText    = VARPTR(szBuf)
                    LVI.cchTextMax = SIZEOF(szBuf)
    
                    FOR col = 0 TO %ListviewColumnCount - 1
                        IF col = %ColumnNumber_Name THEN 'first column means that a new row/item must be inserted
                            szBuf = gProcess_Name(gSortedIndex(row))
                        ELSEIF col = %ColumnNumber_ID THEN
                            szBuf = FORMAT$(gProcess_ID(gSortedIndex(row)))
                        ELSEIF col = %ColumnNumber_ParentProcessID THEN
                            szBuf = FORMAT$(gProcess_ParentProcessID(gSortedIndex(row)))
                        ELSEIF col = %ColumnNumber_CreateTime THEN
                            '-- turn the QUAD createtime into FILETIME so it can be converted into a standard date/time format:
                            IF gProcess_CreateTime(gSortedIndex(row)) > 0 THEN
                                FT.dwLowDateTime  = LO(DWORD, gProcess_CreateTime(gSortedIndex(row)))
                                FT.dwHighDateTime = HI(DWORD, gProcess_CreateTime(gSortedIndex(row)))
                                IF FileTimeToLocalFileTime(FT, LT) <> 0 THEN 'success?
                                    IF FileTimeToSystemTime(LT, ST) <> 0 THEN  'success?
                                        szBuf = FORMAT$(ST.wMonth) & "/" & FORMAT$(ST.wDay) & "/" & FORMAT$(ST.wYear) & " @ " & FORMAT$(ST.wHour, "00") & ":" & FORMAT$(ST.wMinute, "00") & ":" & FORMAT$(ST.wSecond, "00")
                                    END IF
                                END IF
                            ELSE 'System Idle Process and System will not have a valid CreateTime
                                szBuf = "N/A"
                            END IF
                            'szBuf = FORMAT$(gProcess_CreateTime(gSortedIndex(row)))
                        ELSEIF col = %ColumnNumber_RunningTime THEN
                            IF gProcess_RunningTime(gSortedIndex(row)) > 0 THEN
                                szBuf = TRIM$(ConvertSecondsIntoFormattedString(gProcess_RunningTime(gSortedIndex(row)) / 10000000)) 'there are 10 million 100ns intervals in a second
                            ELSE 'System Idle Process and System will always be 0
                                szBuf = "N/A"
                            END IF
                        ELSEIF col = %ColumnNumber_UserTime THEN
                            szBuf = TRIM$(ConvertSecondsIntoFormattedString(gProcess_UserTime(gSortedIndex(row)) / 10000000)) 'there are 10 million 100ns intervals in a second
                        ELSEIF col = %ColumnNumber_KernelTime THEN
                            szBuf = TRIM$(ConvertSecondsIntoFormattedString(gProcess_KernelTime(gSortedIndex(row)) / 10000000)) 'there are 10 million 100ns intervals in a second
                        ELSEIF col = %ColumnNumber_TotalCPUTime THEN
                            szBuf = TRIM$(ConvertSecondsIntoFormattedString(gProcess_TotalCPUTime(gSortedIndex(row)) / 10000000)) 'there are 10 million 100ns intervals in a second
                        ELSEIF col = %ColumnNumber_CPUPercent THEN
                            szBuf = FORMAT$(ROUND(gProcess_CPUPercent(gSortedIndex(row)), 2), "* 0.00") & "%"
                        ELSEIF col = %ColumnNumber_BasePriority THEN
                            IF gProcess_BasePriority(gSortedIndex(row)) > 0 THEN
                                szBuf = FORMAT$(gProcess_BasePriority(gSortedIndex(row)), "* #,")
                            ELSE 'System Idle Process will always have a priority of 0
                                szBuf = "N/A"
                            END IF
                        ELSEIF col = %ColumnNumber_HandleCount THEN
                            szBuf = FORMAT$(gProcess_HandleCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_NumberOfThreads THEN
                            szBuf = FORMAT$(gProcess_NumberOfThreads(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_PeakVirtualSize THEN
                            szBuf = FORMAT$(gProcess_PeakVirtualSize(gSortedIndex(row)) / 1024, "* #,") & " K"
                        ELSEIF col = %ColumnNumber_VirtualSize THEN
                            szBuf = FORMAT$(gProcess_VirtualSize(gSortedIndex(row)) / 1024, "* #,") & " K"
                        ELSEIF col = %ColumnNumber_PageFaultCount THEN
                            szBuf = FORMAT$(gProcess_PageFaultCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_PeakWorkingSetSize THEN
                            szBuf = FORMAT$(gProcess_PeakWorkingSetSize(gSortedIndex(row)) / 1024, "* #,") & " K"
                        ELSEIF col = %ColumnNumber_WorkingSetSize THEN
                            szBuf = FORMAT$(gProcess_WorkingSetSize(gSortedIndex(row)) / 1024, "* #,") & " K"
                        ELSEIF col = %ColumnNumber_PrivatePageCount THEN
                            szBuf = FORMAT$(gProcess_PrivatePageCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_ReadOperationCount THEN
                            szBuf = FORMAT$(gProcess_ReadOperationCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_WriteOperationCount THEN
                            szBuf = FORMAT$(gProcess_WriteOperationCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_OtherOperationCount THEN
                            szBuf = FORMAT$(gProcess_OtherOperationCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_ReadTransferCount THEN
                            szBuf = FORMAT$(gProcess_ReadTransferCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_WriteTransferCount THEN
                            szBuf = FORMAT$(gProcess_WriteTransferCount(gSortedIndex(row)), "* #,")
                        ELSEIF col = %ColumnNumber_OtherTransferCount THEN
                            szBuf = FORMAT$(gProcess_OtherTransferCount(gSortedIndex(row)), "* #,")
                        END IF
    
    
                        IF col = 0 THEN 'first column means that a new row/item must be inserted
                            LVI.mask     = %LVIF_TEXT OR %LVIF_PARAM 'OR %LVIF_STATE
                            LVI.iSubItem = 0 'this must be zero when inserting a new item
                            LVI.lParam   = gProcess_ID(gSortedIndex(row)) 'store the PID so the process can be identified when selecting a row
                            IF SendMessage(hListview, %LVM_INSERTITEM, 0, VARPTR(LVI)) = -1 THEN MSGBOX "LVM_INSERTITEM failed!", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
                        ELSE
                            LVI.mask     = %LVIF_TEXT
                            LVI.iSubItem = col '0 based
                            IF SendMessage(hListview, %LVM_SETITEM, 0, VARPTR(LVI)) = %FALSE THEN MSGBOX "LVM_SETITEM failed!", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
                        END IF
    
    'this is a failed attempt at trying to not use %LVM_DELETEALLITEMS, and instead reuse existing rows:
    ' %LVM_DELETEALLITEMS is the reason why the scrollbars reset each time. not deleting all items fixes that issue
    ' but it ulimately ends up causing flicker.. don't know why.
    '                    IF col = 0 THEN 'first column means that a new row/item must be inserted
    '                        IF row <= PreviousNumberOfRowsInListview THEN   'are there enough rows available in the listview to add this item?
    '                            LVI.mask     = %LVIF_TEXT
    '                            LVI.iSubItem = col '0 based
    '                            IF SendMessage(hListview, %LVM_SETITEM, 0, VARPTR(LVI)) = %FALSE THEN MSGBOX "LVM_SETITEM failed!", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
    '                        else  'not enough rows. need to add another one
    '                            INCR PreviousNumberOfRowsInListview
    '                            LVI.mask = %LVIF_TEXT OR %LVIF_PARAM OR %LVIF_STATE
    '                            LVI.iSubItem = 0 'this must be zero when inserting a new item
    '                            IF SendMessage(hListview, %LVM_INSERTITEM, 0, VARPTR(LVI)) = -1 THEN MSGBOX "LVM_INSERTITEM failed!", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
    '                        END IF
    '                    ELSE
    '                        LVI.mask     = %LVIF_TEXT
    '                        LVI.iSubItem = col '0 based
    '                        IF SendMessage(hListview, %LVM_SETITEM, 0, VARPTR(LVI)) = %FALSE THEN MSGBOX "LVM_SETITEM failed!", %MB_ICONERROR OR %MB_SYSTEMMODAL, $Program
    '                    END IF
    
                    NEXT 'col
                NEXT 'row
    
    
    
                '-- allow the listview to redraw, now that the items are done being updated:
                SendMessage(hListview, %WM_SETREDRAW, %TRUE, 0)
    
                '-- set horizontal and vertical scrollbar position (%WM_SETREDRAW must be %TRUE before this point, or it may not scroll correctly):
                ' unfortunately, this will cause flicker, but it's necessary if we want to retain the original scroll position.
                SendMessage(hListView, %LVM_SCROLL, LvSbPosX, LvSbPosY)
    
                '-- tell Windows to NOT do any erasing/drawing for the client area of the window behind the listview:
                '   this is required in order to prevent the listbox from flickering.
                '   for this program, the listview covers the entire client area, so we can just pass an empty RECT; other programs may need to get the RECT occupied by the listview
                ValidateRect(CBHNDL, BYVAL 0)
    
                '-- tell Windows that the listview needs to be redrawn:
                '   this MUST come AFTER the window's client area behind the listview has been validated!  ValidateRect(CBHNDL, BYVAL 0)
                '   we need to determine if the listview's background should be erased..
                '-- are there extra rows at the bottom? (has the number of running processes decreased since the last refresh?)
                IF PreviousNumberOfRowsInListview > ProcessCount THEN
                    LOCAL ItemPT AS POINTL
                    LOCAL ListviewRECT AS RECTL
                    GetClientRect(hListview, ListviewRECT)
                    SendMessage(hListView, %LVM_GETITEMPOSITION, ProcessCount + 1, VARPTR(ItemPT))
                    '-- does the row immediately after the last item appear above the bottom of the listview?
                    IF ItemPT.y < ListviewRECT.nBottom THEN 'if yes, then the Erase flag needs to be %TRUE so the extra row(s) will be cleared (this will cause flicker, but it has to be done..)
                        InvalidateRect(hListview, BYVAL 0, %TRUE)   'send %TRUE so the listview's background will be erased (clearing the extra bottom rows that we don't want to see anymore)
                    ELSE    'if no, then we do not need to erase the background (no flicker! yay!)
                        InvalidateRect(hListview, BYVAL 0, %FALSE)  'send %FALSE to reduce flickering (the listview's background will NOT be erased)
                    END IF
                ELSE
                    InvalidateRect(hListview, BYVAL 0, %FALSE)  'send %FALSE to reduce flickering (the listview's background will NOT be erased)
                END IF
    
            '------------------------------------------------------------------------------------------------------
    
            CASE %WM_INITDIALOG
                hListview = GetDlgItem(CBHNDL, %IDC_Listview)
    
                '-- change the font for the listview. this only adjusts the row height; the font itself will not be changed (that must be done in %NM_CUSTOMDRAW):
                hDC = GetDC(hListview) 'get DC to the listview. Use ReleaseDC once it's no longer needed.
                lf.lfHeight   = -MulDiv(7, GetDeviceCaps(hDC, %LOGPIXELSY), 72)
                lf.lfFaceName = "Tahoma" & CHR$(0)
                lf.lfWeight   = %FW_NORMAL '%FW_BOLD
                hListviewFont = CreateFontIndirect(lf)
                SendMessage(hListview, %WM_SETFONT, hListviewFont, MAKLNG(%TRUE, 0))
                'DeleteObject(hListviewFont)  'delete the custom font (actually, don't delete it because we need to reuse it in %NM_CUSTOMDRAW)
                ReleaseDC(hListview, hDC) 'release the DC
    
                '-- initialize the listview (column headers/sizes):
                CALL InitializeListView(hListview)
    
                REDIM gColumnSortDirection(%ListviewColumnCount - 1) AS GLOBAL LONG
    
                CALL SetTimer(CBHNDL, %TIMERID, 2000, %NULL)
                '-- post a message to immediately refresh the listview. this avoids the startup delay (waiting for the timer):
                CALL PostMessage(CBHNDL, %WM_REFRESHLISTVIEW, 0, 0)
    
            '------------------------------------------------------------------------------------------------------
    
            CASE %WM_NCACTIVATE
                STATIC hWndSaveFocus AS DWORD
                IF ISFALSE CBWPARAM THEN
                    hWndSaveFocus = GetFocus()
                ELSEIF hWndSaveFocus THEN
                    SetFocus(hWndSaveFocus)
                    hWndSaveFocus = 0
                END IF
    
            '------------------------------------------------------------------------------------------------------
    
            CASE %WM_SIZE   'called after the dialog changes size
                SetWindowPos(hListview, 0, 0, 0, LO(WORD, CBLPARAM), HI(WORD, CBLPARAM), %SWP_NOMOVE OR %SWP_NOOWNERZORDER)
                FUNCTION = 0 : EXIT FUNCTION
    
            '------------------------------------------------------------------------------------------------------
    
            CASE %WM_COMMAND
                SELECT CASE AS LONG CBCTL
                    CASE %IDC_Listview
    
                    CASE %IDCANCEL  'ESC key
                        DIALOG END CBHNDL, 0
    
                END SELECT
    
            '------------------------------------------------------------------------------------------------------
    
            CASE %WM_CLOSE  'dialog is about to close
                KillTimer(CBHNDL, %TIMERID)
    
        END SELECT
    
    END FUNCTION
    
    
    '============================================================================================================
    '== ShowTaskManDialog =======================================================================================
    '============================================================================================================
    FUNCTION ShowTaskManDialog(BYVAL hParent AS DWORD) AS LONG
        LOCAL lRslt AS LONG
    #PBFORMS BEGIN DIALOG %IDD_TaskManDialog->->
        LOCAL hDlg  AS DWORD
    
        DIALOG NEW hParent, "Task Manager Emulator", 259, 220, 460, 267, %WS_POPUP OR %WS_BORDER OR %WS_DLGFRAME OR %WS_THICKFRAME OR %WS_CAPTION OR %WS_SYSMENU OR %WS_MINIMIZEBOX OR %WS_MAXIMIZEBOX OR %WS_CLIPSIBLINGS OR %WS_VISIBLE OR _
            %DS_MODALFRAME OR %DS_CENTER OR %DS_3DLOOK OR %DS_NOFAILCREATE OR %DS_SETFONT, %WS_EX_CONTROLPARENT OR %WS_EX_TOPMOST OR %WS_EX_LEFT OR %WS_EX_LTRREADING OR %WS_EX_RIGHTSCROLLBAR, TO hDlg
        CONTROL ADD "SysListView32", hDlg, %IDC_Listview, "SysListView32_1", 0, 0, 460, 267, %WS_CHILD OR %WS_VISIBLE OR %WS_TABSTOP OR %LVS_REPORT OR %LVS_SINGLESEL, %WS_EX_CLIENTEDGE OR %WS_EX_LEFT OR %WS_EX_RIGHTSCROLLBAR
    #PBFORMS END DIALOG
    
        DIALOG SHOW MODAL hDlg, CALL ShowTaskManDialogProc TO lRslt
    
    #PBFORMS BEGIN CLEANUP %IDD_TaskManDialog
    #PBFORMS END CLEANUP
    
        FUNCTION = lRslt
    END FUNCTION
    
    
    
    
    
    
    
    
    
    
    '============================================================================================================
    '== INITIALIZELISTVIEW ======================================================================================
    '============================================================================================================
    SUB InitializeListView (BYVAL hListview AS DWORD)
        LOCAL col AS LONG
        LOCAL tLVC AS LVCOLUMN
        LOCAL szColumnTxt AS ASCIIZ * 100   'the text in the column header
        LOCAL szSampleTxt AS ASCIIZ * 1000  'the longest string of text that a column's subitems will contain (this is used to set column width)
    
        '-- modify the listview style:
        ListView_SetExtendedListViewStyle(hListview, ListView_GetExtendedListViewStyle(hListview) OR _  'get the existing extended style
                                                     %LVS_EX_FULLROWSELECT  OR _ 'select an entire row (instead of a single cell)
                                                     %LVS_EX_DOUBLEBUFFER   OR _ 'this is supposed to reduce flicker (only with WinXP or greater)
                                                     %LVS_EX_HEADERDRAGDROP)     'allow the user to reorder the columns via drag and drop (the listview automatically takes care of everything for us.)
                                                     '%LVS_EX_GRIDLINES OR _     'show lines inbetween all the cells (may causes noticable flickering when updating the cells)
    
        '-- initialize the columns:
        tLVC.mask    = %LVCF_FMT OR %LVCF_SUBITEM OR %LVCF_TEXT OR %LVCF_WIDTH
        tLVC.pszText = VARPTR(szColumnTxt)
    
        FOR col = 0 TO %ListviewColumnCount - 1
            IF col = %ColumnNumber_Name THEN
                szColumnTxt = "Process Name"
                szSampleTxt = "System Idle Process"
                tLVC.fmt = %LVCFMT_LEFT
            ELSEIF col = %ColumnNumber_ID THEN
                szColumnTxt = "PID"
                szSampleTxt = "00000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_ParentProcessID THEN
                szColumnTxt = "Parent PID"
                szSampleTxt = "00000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_CreateTime THEN
                szColumnTxt = "Created Date/Time"
                szSampleTxt = "10/00/2000 @ 00:00:00"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_RunningTime THEN
                szColumnTxt = "Running Time"
                szSampleTxt = "00d00h00m00s000ms"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_UserTime THEN
                szColumnTxt = "User CPU Time"
                szSampleTxt = "00h00m00s000ms"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_KernelTime THEN
                szColumnTxt = "Kernel CPU Time"
                szSampleTxt = "00d00h00m00s000ms"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_TotalCPUTime THEN
                szColumnTxt = "Total CPU Time"
                szSampleTxt = "00d00h00m00s000ms"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_CPUPercent THEN
                szColumnTxt = "CPU %"
                szSampleTxt = "100.00%"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_BasePriority THEN
                szColumnTxt = "Priority"  'Base Priority
                szSampleTxt = "N/A"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_HandleCount THEN
                szColumnTxt = "Handles"
                szSampleTxt = "1,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_NumberOfThreads THEN
                szColumnTxt = "Threads"
                szSampleTxt = "000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_PeakVirtualSize THEN
                szColumnTxt = "Peak Virtual Size"
                szSampleTxt = "000,000 K"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_VirtualSize THEN
                szColumnTxt = "Virtual Size"
                szSampleTxt = "000,000 K"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_PageFaultCount THEN
                szColumnTxt = "Page Faults"
                szSampleTxt = "00,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_PeakWorkingSetSize THEN
                szColumnTxt = "Peak Mem Usage" 'Peak Working Set
                szSampleTxt = "000,000 K"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_WorkingSetSize THEN
                szColumnTxt = "Mem Usage" 'Working Set
                szSampleTxt = "000,000 K"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_PrivatePageCount THEN
                szColumnTxt = "Private Page Count"
                szSampleTxt = "00,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_ReadOperationCount THEN
                szColumnTxt = "Read Ops Count"
                szSampleTxt = "00,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_WriteOperationCount THEN
                szColumnTxt = "Write Ops Count"
                szSampleTxt = "00,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_OtherOperationCount THEN
                szColumnTxt = "Other Ops Count"
                szSampleTxt = "00,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_ReadTransferCount THEN
                szColumnTxt = "Read Trans Count"
                szSampleTxt = "00,000,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_WriteTransferCount THEN
                szColumnTxt = "Write Trans Count"
                szSampleTxt = "00,000,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            ELSEIF col = %ColumnNumber_OtherTransferCount THEN
                szColumnTxt = "Other Trans Count"
                szSampleTxt = "00,000,000,000"
                tLVC.fmt = %LVCFMT_RIGHT
            END IF
    
            '-- force column 0 to be left-aligned:
            IF col = 0 THEN tLVC.fmt = %LVCFMT_LEFT    'the leftmost column in a list view control MUST be left aligned!!
    
            '-- get the required width of the column so the entire sample text can be displayed without being clipped:
            tLVC.cx = SendMessage(hListview, %LVM_GETSTRINGWIDTH, 0, VARPTR(szSampleTxt))
            tLVC.cx = tLVC.cx + 12  'MS says that LVM_GETSTRINGWIDTH returns a number too small and requires padding to avoid text clipping (+12 seems to work ok)
    
            '-- insert the column:
            SendMessage(hListview, %LVM_INSERTCOLUMN, col, VARPTR(tLVC))
        NEXT col
    
    END SUB
    Last edited by Bud Meyer; 23 Oct 2007, 08:27 PM. Reason: improved code

  • #2
    Hey Bud,

    To avoid the listview horizontal and vertical scrollbar
    position to be resetted at each timer call
    one may consider adding following code

    Great work by the way...

    Pierre

    Code:
    After this line: CASE %WM_REFRESHLISTVIEW  'this message is sent from %WM_INITDIALOG and %WM_TIMER, so the listview can refresh at startup or during the timer
    Add            : LOCAL LvSbPosX AS LONG
    Add            : LOCAL LvSbPosY AS LONG
    Add            : LOCAL LvRcSize AS RECT
    Add            : SendMessage(hListView, %LVM_GETITEMRECT, BYVAL %NULL, BYVAL VARPTR(LvRcSize))
    Add            : LvSbPosY = GetScrollPos(hListView, %SB_VERT) * (LvRcSize.nBottom - LvRcSize.nTop) 'Get vertical scrollbar position
    Add            : LvSbPosX = GetScrollPos(hListView, %SB_HORZ) 'Get horizontal scrollbar position
     
    Then...    
    After this line: '-- allow the listview to redraw:
    add            : PostMessage(hListView, %LVM_SCROLL, LvSbPosX, LvSbPosY) 'Set horizontal and vertical scrollbar position

    Comment


    • #3
      That's a nice improvement, Pierre. It was annoying for the scrollbars to reset each time (which I believe is due to the use of %LVM_DELETEALLITEMS).

      A much more annoying issue was the refresh flicker, which I have basically eliminated thanks to the following line:
      Code:
      ValidateRect(CBHNDL, BYVAL 0)
      Unfortunately, your scrolling trick still causes flicker when the horizontal scrollbar is not furthest left, and I haven't figured out a way to prevent that.

      I've also updated the code so that the columns will automatically figure out their widths by measuring some sample text. Now the font size can be changed without having to readjust the starting widths of the columns.

      Comment

      Working...
      X