Что нового

Новый _ArrayDisplay

musicstashall

Знающий
Сообщения
310
Репутация
5
Версия AutoIt
3.3.14.2
Версия
3.0
Представляю модификацию стандартной функции просмотра массивов _ArrayDisplay
Вообще, всякий раз, открывая массив для обозрения, я наблюдал неформатированные строки, как на этом скриншоте:



Поэтому я первым делом решил установить шрифт для ListView, в результате видим совсем иную картину:



Плюс к этому я добавил в стиль ListView двойную буферизацию $LVS_EX_DOUBLEBUFFER, так стало мягче скроллиться, без мерцания.
Но самое главное в другом. Кто часто пользуется массивами, тот понимает, насколько бесполезной становится эта функция, если в массиве содержатся другие массивы, и вы не можете посмотреть содержимое ячейки, содержащей массив:


Я решил исправить такое упущение и теперь вы можете вызвать для просмотра содержимое ячейки с дочерним массивом двойным кликом мыши:


Мне самому очень нужно было видеть содержимое дочерних массивов и я решил это реализовать.
Реализованы нюансы, например, чтобы повторно не открывался массив, если уже открыт, вместо этого, окно с этим массивом будет активировано. После закрытия окна с массивом, данные об этом окне удаляются из переменой и массив можно снова открыть. Все глобальные переменные «виртуальные», создаются функцией Assign и читаются функцией Eval, чтобы не засорять исходный скрипт. В функцию _ArrayDisplay добавлен восьмой параметр для родительского окна, чтобы открываемые окна с массивами сохраняли Z-порядок.

ПС: функции чтения и преобразования строк ( из опорных переменных $sid) сделаны на скорую руку, вероятно, можно будет доработать и сделать более красиво.

Сообщение автоматически объединено:

Сделал новую версию. Отказался от использования GUIRegisterMsg($WM_NOTIFY, "WM_ARRAYDISPLAY_NOTIFY"), так как это отменяло бы предыдущую подобную регистрацию функции у пользователя. Вместо этого использовал глобальные хуки с помощью DllCallbackRegister("CALL_NOTIFY", "int", "hwnd;uint;wparam;lparam"). Далее, в предыдущем варианте каждое дочернее окно после создания «крутилось» в своем цикле, а потому при закрытии окон, всегда закрывалось только последнее открытое. Поэтому, после создания любого дочернего окна, теперь делается выход из функции перед циклом, то есть, в цикле «крутится» только родительское окно.
Добавил получение индекса массива не по индексу пункта ListView а по тексту в пункте, чтобы избежать неверного чтения, если пункты были отсортированы.

Изменения в оригинальном коде:
Код:
#include "StructureConstants.au3" ; Added to the beginning of the script

Func __ArrayDisplay_Share(Const ByRef $aArray, $sTitle = Default, $sArrayRange = Default, $iFlags = Default, $vUser_Separator = Default, $sHeader = Default, $iMax_ColWidth = Default, $hUser_Function = Default, $bDebug = True, $iParent = 0)

    ;=============== ADD MODIFICATION =============
 
    If __ArrayDisplay_CallBackRegisrer($hGUI, $iParent, $aArray) Then Return
 
    ;=============== END MODIFICATION =============
 
    ; Switch to GetMessage mode
    Local $iOnEventMode = Opt("GUIOnEventMode", 0), $iMsg
 
    While 1

        $iMsg = GUIGetMsg() ; Variable needed to check which "Copy" button was pressed
        Switch $iMsg
            Case $_ARRAYCONSTANT_GUI_EVENT_CLOSE
            
                ;================ MODIFICATION ================
            
                Local $hWnd = WinGetHandle('')
            
                Local $hPrnt = __ArrayDisplay_CallBackFree($hWnd)
            
                If Eval($hGUI) Then
                    __ArrayDisplay_DelhWnd($hWnd, $hPrnt)
                Else
                    ExitLoop
                EndIf
            
                ;=============== END MODIFICATION =============
       WEnd
EndFunc   ;==>__ArrayDisplay_Share


Добавленные функции:

Код:
#Region Modification functions

Func CALL_NOTIFY($hWnd, $iMsg, $iwParam, $ilParam)
    Local $aHandle_Proc = Eval('arraydisplayprocdata')
    Switch $iMsg
        Case 0x4E ; WM_NOTIFY
            Local $tNMHDR = DllStructCreate($tagNMHDR, $ilParam)
            Local $hWndFrom = HWnd(DllStructGetData($tNMHDR, "hWndFrom"))
            Local $idListView = DllStructGetData($tNMHDR, "IDFrom")
            Local $iCode = DllStructGetData($tNMHDR, "Code")
            Switch $iCode
                Case -3 ; NM_DBLCLK
                    ; _WinAPI_GetMousePos
                    Local $iMode = Opt("MouseCoordMode", 1)
                    Local $aPos = MouseGetPos()
                    Opt("MouseCoordMode", $iMode)
                    Local $tPoint = DllStructCreate($tagPOINT)
                    DllStructSetData($tPoint, "X", $aPos[0])
                    DllStructSetData($tPoint, "Y", $aPos[1])
                    DllCall("user32.dll", "bool", "ScreenToClient", "hwnd", $hWndFrom, "struct*", $tPoint)
                    If @error Then Return
                    Local $tTest = DllStructCreate($tagLVHITTESTINFO)
                    DllStructSetData($tTest, "X", DllStructGetData($tPoint, "X"))
                    DllStructSetData($tTest, "Y", DllStructGetData($tPoint, "Y"))
                    ; _GUICtrlListView_SubItemHitTest($hWnd)
                    GUICtrlSendMsg($idListView, 0x00001039, 0, DllStructGetPtr($tTest))
                    ; _GUICtrlListView_GetItemText
                    Local $tBuffer = DllStructCreate("char Text[4096]")
                    Local $pBuffer = DllStructGetPtr($tBuffer)
                    Local $tItem = DllStructCreate($tagLVITEM)
                    DllStructSetData($tItem, "SubItem", 0)
                    DllStructSetData($tItem, "TextMax", 4096)
                    Local $pItem = DllStructGetPtr($tItem)
                    DllStructSetData($tItem, "Text", $pBuffer)
                    GUICtrlSendMsg($idListView, 0x1000 + 45, DllStructGetData($tTest, "Item"), $pItem)
                    Local $iItem = Int(StringReplace(DllStructGetData($tBuffer, "Text"), 'Row ', ''))
                    If $iItem < 0 Or DllStructGetData($tTest, "SubItem") < 1 Then Return
                    Local $iIndex = _ArraySearch($aHandle_Proc, $hWnd, 1, 0, 0, 0, 1, 0)
                    Local $aArray = $aHandle_Proc[$iIndex][5]
                    If UBound($aArray) < $iItem + 1 Then Return
                    Local $sid = Eval($hWnd)
                    Local $sCaption = 'Array[' & $iItem & ']'
                    Switch UBound($aArray, 0)
                        Case 1
                            If Not IsArray($aArray[$iItem]) Then Return
                            If __ArrayDisplay_IsOpen($sid, $sCaption) Then Return WinActivate(HWnd(__ArrayDisplay_GethWnd($sid, $sCaption)))
                            Assign($hWnd, $sid ? $sid & '|' & $sCaption : $sCaption, 2) ; We write the cell of the opened array Array[n][n] to the string
                            __ArrayDisplay_Share($aArray[$iItem], $sCaption, Default, Default, Default, Default, Default, 0, False, $hWnd)
                        Case 2
                            If Not IsArray($aArray[$iItem][DllStructGetData($tTest, "SubItem") -1]) Then Return
                            $sCaption &= '[' & DllStructGetData($tTest, "SubItem") -1 & ']'
                            If __ArrayDisplay_IsOpen($sid, $sCaption) Then Return WinActivate(HWnd(__ArrayDisplay_GethWnd($sid, $sCaption)))
                            Assign($hWnd, $sid ? $sid & '|' & $sCaption : $sCaption, 2) ; We write the cell of the opened array Array[n][n] to the string
                            __ArrayDisplay_Share($aArray[$iItem][DllStructGetData($tTest, "SubItem") -1], $sCaption, Default, Default, Default, Default, Default, 0, False, $hWnd)
                    EndSwitch
                Case -108  
                    __ArrayDisplay_SortItems($idListView, GUICtrlGetState($idListView))
            EndSwitch
    EndSwitch
  
    For $i = 1 To $aHandle_Proc[0][0]
        ; And pass other messages to original WindowProc
        If $hWnd = $aHandle_Proc[$i][0] Then Return __ArrayDisplay_CallWindowProc($aHandle_Proc[$i][1], $hWnd, $iMsg, $iwParam, $ilParam)
    Next
EndFunc

; _WinAPI_CallWindowProc
Func __ArrayDisplay_CallWindowProc($pPrevWndFunc, $hWnd, $iMsg, $wParam, $lParam)
    Local $aResult = DllCall("user32.dll", "lresult", "CallWindowProc", "ptr", $pPrevWndFunc, "hwnd", $hWnd, "uint", $iMsg, _
            "wparam", $wParam, "lparam", $lParam)
    If @error Then Return SetError(@error, @extended, -1)

    Return $aResult[0]
EndFunc   ;==>_WinAPI_CallWindowProc

; Registering the callback function
Func __ArrayDisplay_CallBackRegisrer($hWnd, $iParent, $aArray)
 
    If Not Eval($iParent) Then
        ; The very first window is opened, which will be the parent for all subsequent windows.
        ; All child windows don't come here anymore
        Local $aHandle_Proc[1][6] = [[0]]
        Assign('arraydisplayprocdata', $aHandle_Proc, 2) ; This is a global variable in which our array with data will be stored
    EndIf
 
    Local $aHandle_Proc = Eval('arraydisplayprocdata') ; get our array from the variable
 
    ; then a callback is registered and data is written to the array
    $aHandle_Proc[0][0] += 1
    ReDim $aHandle_Proc[$aHandle_Proc[0][0] + 1][6]
    $aHandle_Proc[$aHandle_Proc[0][0]][0] = $hWnd
    $aHandle_Proc[$aHandle_Proc[0][0]][2] = DllCallbackRegister("CALL_NOTIFY", "int", "hwnd;uint;wparam;lparam")
    $aHandle_Proc[$aHandle_Proc[0][0]][3] = DllCallbackGetPtr($aHandle_Proc[$aHandle_Proc[0][0]][2])
    $aHandle_Proc[$aHandle_Proc[0][0]][4] = $iParent
    $aHandle_Proc[$aHandle_Proc[0][0]][5] = $aArray
    Local $sFuncName = "SetWindowLongW"
    If @AutoItX64 Then $sFuncName = "SetWindowLongPtrW"
    Local $aResult = DllCall("user32.dll", "long_ptr", $sFuncName, "hwnd", $hWnd, "int", -4, "long_ptr", $aHandle_Proc[$aHandle_Proc[0][0]][3])
    If @error Then $aResult = 0
    If Not @error Then $aResult = $aResult[0]
    $aHandle_Proc[$aHandle_Proc[0][0]][1] = $aResult
    If $aHandle_Proc[$aHandle_Proc[0][0]][1] = 0 Then $aHandle_Proc[0][0] -= 1
    ReDim $aHandle_Proc[$aHandle_Proc[0][0] + 1][6]
 
    ; Put the modified array back into the global variable
    Assign('arraydisplayprocdata', $aHandle_Proc, 2)
 
    If Eval($iParent) Then
        ; Here we write all the child windows to be opened in one line in this way Array[n][n];0x0000000000,
        ; where Array is the opened array cell in the parent window, after the semicolon is the hWnd of the parent window
        ; Write the string to a global variable named hWnd of the parent window
        Assign($iParent, Eval($iParent) & ';' & $hWnd, 2)
        ;ConsoleWrite('+' & $iParent & @TAB & 'SID' & @TAB & Eval($iParent) & @CR)
        Return $hWnd
    EndIf
 
EndFunc

; Recursive callback cleanup
Func __ArrayDisplay_CallBackFree($hWnd)
    Local $aHandle_Proc = Eval('arraydisplayprocdata')
    Local $sFuncName = "SetWindowLongW"
    If @AutoItX64 Then $sFuncName = "SetWindowLongPtrW"
    ;ConsoleWrite('- DELETE ' & $hWnd & @TAB & Eval($hWnd) & @CR)
    Local $aSplitData = StringSplit(Eval($hWnd), '|', 2)
    Assign($hWnd, 0, 2)
    ReDim $aSplitData[UBound($aSplitData) + 1]
    $aSplitData[UBound($aSplitData) -1] = $hWnd
    Local $iIndex, $hParent, $hChld
    For $sVal In $aSplitData
        $hChld = __ArrayDisplay_GethWnd($sVal)
        If Eval($hChld) Then
            __ArrayDisplay_CallBackFree($hChld)
            $aHandle_Proc = Eval('arraydisplayprocdata')
            ContinueLoop
        EndIf
        If Not $hChld Then ContinueLoop
        $iIndex = _ArraySearch($aHandle_Proc, $hChld, 1, 0, 0, 0, 1, 0)
        If $iIndex == -1 Then ContinueLoop
        DllCall("user32.dll", "long_ptr", $sFuncName, "hwnd", $aHandle_Proc[$iIndex][0], "int", -4, "long_ptr", $aHandle_Proc[$iIndex][1])
        DllCallbackFree($aHandle_Proc[$iIndex][2])
        $hParent = $aHandle_Proc[$iIndex][4]
        _ArrayDelete($aHandle_Proc, $iIndex)
        $aHandle_Proc[0][0] -= 1
    Next
    Assign('arraydisplayprocdata', $aHandle_Proc, 2)
    Return $hParent
EndFunc

; Checks whether this array cell has been opened for viewing
Func __ArrayDisplay_IsOpen($sid, $sSub)
    Return (StringInStr($sid, $sSub) > 0)
EndFunc

; Retrieves from the hWnd string the window associated with the desired part of the string
Func __ArrayDisplay_GethWnd($sid, $sSub = '')
    Local $aData = StringRegExp($sid, '[0-9A-Fa-f]{16}|[0-9A-Fa-f]{8}', 1, StringInStr($sid, $sSub))
    If @error Then Return 0
    Return HWnd('0x' & $aData[0])
EndFunc

; Removes the part with hWnd from the string
; Writes the string to a variable with the name of the parent window
Func __ArrayDisplay_DelhWnd($hWnd, $hParent)
    Local $sid = Eval($hParent)
    $sid = StringRegExpReplace($sid, '(Array......;' & $hWnd & '|Array...;' & $hWnd & ')', '')
    $sid = StringReplace($sid, '||', '|')
    If $sid == '|' Then $sid = ''
    ;ConsoleWrite('- DEL HWND ' & $hWnd &@TAB & $sid & @CR)
    Assign($hParent, $sid, 2)
    GUIDelete($hWnd)
    WinActivate($hParent)
EndFunc

#EndRegion End modification region




Готовые файлы для замены оригинальных в папке AutoIt3\include:
 
Автор
musicstashall

Вложения

  • Array.au3
    79.2 КБ · Просмотры: 8
  • ArrayDisplayInternals.au3
    41.1 КБ · Просмотры: 8
Последнее редактирование:

Prog

Осваивающий
Сообщения
414
Репутация
45
Замечательно что сделали такой инструмент, но сколько же в коде магических чисел!
Через некоторое время забудете что означает каждое из них и придется заново разбираться в коде.

Может лучше было сделать как в проводнике винды. При клике по элементу массива его содержимое отображается в том же окне и присутствует кнопка Назад, возвращающая на элемент выше.
 
Автор
M

musicstashall

Знающий
Сообщения
310
Репутация
5
Через некоторое время забудете что означает каждое из них и придется заново разбираться в коде.
Я согласен, набросал код за два дня, его следует причесать)
Может лучше было сделать как в проводнике винды.
Так не считаю. При открывании дочерних массивов, всегда необходимо иметь перед глазами родительский объект. По крайней мере делал под себя.
Сообщение автоматически объединено:

Выкладываю версию 3.
Весь код причесал и упорядочил. В коде подписал комментарии. Скрипт оптимизировал, исправил ошибки:
1. Если закрывалось окно, имеющее открытыми дочерние окна, то эти дочерние окна закрывались без очистки CallBack, что приводило к отмене функций WM_NOTIFY пользователя, если были зарегистрированы. CallBack очищается рекурсивно с проверкой дочерних окон, сколько бы ни было.​
2. Дочерние окна открываются не в цикле, как было раньше, а непосредственно в функции CALL_NOTIFY, что позволило заметно прибраться в коде.​

Файлы в раздаче обновлены
 
Последнее редактирование:
Верх