Что нового

[Элементы GUI] Динамическое увеличение массива строк в ListView (?)

The Dream

Новичок
Сообщения
393
Репутация
3
Версия AutoIt:

3.3.8.1

Описание проблемы:

Есть исходный код в котором цвет, а также фон "области" в списке ListView задается с помощью специального массива. Проблема заключается в том что из-за отсутствия должных знаний я к сожалению так и не смог понять как мне динамически расширять массив в зависимости от количества строк. Надеюсь я доступно описал проблему :smile:

Исходный код:

Код:
#Include <GUIConstantsEx.au3>
#Include <GUIListView.au3>
#Include <WindowsConstants.au3>

Dim $ColorData[5][2][2] ; если размер заранее не известен?
For $i = 0 To UBound($ColorData) - 1
    For $j = 0 To UBound($ColorData, 2) - 1
        $ColorData[$i][$j][0] = 0
        $ColorData[$i][$j][1] = 0xFFFFFF
    Next
Next


$hGUI = GUICreate('Test', 300, 200)
$hListView = _GUICtrlListView_Create($hGUI, 'Items|SubItems', 10, 10, 280, 180, $LVS_REPORT, $WS_EX_CLIENTEDGE)
_GUICtrlListView_SetExtendedListViewStyle($hListView, BitOR($LVS_EX_DOUBLEBUFFER, $LVS_EX_GRIDLINES, $LVS_EX_FULLROWSELECT, $WS_EX_CLIENTEDGE))

_GUICtrlListView_AddItem($hListView, 'Item')
_GUICtrlListView_SetItemColors($hListView, 0, 0, 0)
_GUICtrlListView_AddSubItem($hListView, 0, 'Error', 1)
_GUICtrlListView_SetItemColors($hListView, 0, 1, 0, 0x0000FF)
_GUICtrlListView_AddItem($hListView, 'Error2')
_GUICtrlListView_SetItemColors($hListView, 1, 0, 0, 0x0000FF)
_GUICtrlListView_AddItem($hListView, 'Item')
_GUICtrlListView_SetItemColors($hListView, 2, 0, 0, 0xFFFFFF)
_GUICtrlListView_AddItem($hListView, 'Item')
_GUICtrlListView_SetItemColors($hListView, 3, 0, 0, 0xFFFFFF)
_GUICtrlListView_AddItem($hListView, 'Item666')
_GUICtrlListView_SetItemColors($hListView, 4, 0, 0, 0x0000FF)

GUIRegisterMsg($WM_NOTIFY, 'WM_NOTIFY')
GUISetState()

Do
Until GUIGetMsg() = $GUI_EVENT_CLOSE

Func _GUICtrlListView_SetItemColors($hWnd, $iItem, $iSubItem, $iColor, $iBkColor = 0xFFFFFF, $fRedraw = False)
    If ($iItem < 0) Or ($iItem > UBound($ColorData) - 1) Then
        Return 0
    EndIf
    If ($iSubItem < 0) Or ($iSubItem > UBound($ColorData, 2) - 1) Then
        Return 0
    EndIf
    $ColorData[$iItem][$iSubItem][0] = $iColor
    $ColorData[$iItem][$iSubItem][1] = $iBkColor
    If $fRedraw Then
        _GUICtrlListView_RedrawItems($hWnd, $iItem, $iItem)
    EndIf
    Return 1
EndFunc   ;==>_GUICtrlListView_SetItemColors

Func WM_NOTIFY($hWnd, $iMsg, $wParam, $lParam)

    Local $tNMHDR = DllStructCreate($tagNMHDR, $lParam)
    Local $hWndFrom = DllStructGetData($tNMHDR, 'hWndFrom')
    Local $iCode = DllStructGetData($tNMHDR, 'Code')

    Switch $hWndFrom
        Case $hListView
            Switch $iCode
                Case $NM_CUSTOMDRAW

                    Local $tNMLVCD = DllStructCreate($tagNMLVCUSTOMDRAW, $lParam)
                    Local $iDrawStage = DllStructGetData($tNMLVCD, 'dwDrawStage')
                    Local $iItem = DllStructGetData($tNMLVCD, 'dwItemSpec')
                    Local $iSubItem = DllStructGetData($tNMLVCD, 'iSubItem')

                    Switch $iDrawStage
                        Case $CDDS_PREPAINT
                            Return $CDRF_NOTIFYITEMDRAW
                        Case $CDDS_ITEMPREPAINT
                            Return $CDRF_NOTIFYSUBITEMDRAW
                        Case BitOR($CDDS_ITEMPREPAINT, $CDDS_SUBITEM)
                            DllStructSetData($tNMLVCD, 'clrTextBk', $ColorData[$iItem][$iSubItem][1])
                            DllStructSetData($tNMLVCD, 'clrText', $ColorData[$iItem][$iSubItem][0])
                            Return $CDRF_NEWFONT
                    EndSwitch
            EndSwitch
    EndSwitch
    Return $GUI_RUNDEFMSG
EndFunc   ;==>WM_NOTIFY


Примечания:

Это пример (код) уважаемого мной модератора Y's-a (просто не помню как точно пишется его ник)
 

InnI

AutoIT Гуру
Сообщения
4,922
Репутация
1,432
The Dream [?]
динамически расширять массив в зависимости от количества строк
Код:
ReDim $ColorData[_GUICtrlListView_GetItemCount($hListView)][_GUICtrlListView_GetColumnCount($hListView)][2]
 
Автор
T

The Dream

Новичок
Сообщения
393
Репутация
3
InnI, это можно использовать прямо в работающем коде? То есть увеличивать число строк прямо на ходу... ?
 

InnI

AutoIT Гуру
Сообщения
4,922
Репутация
1,432
The Dream [?]
увеличивать число строк прямо на ходу
Ну, да. Одна функция вернёт текущее количество строк, другая - количество столбцов. Соответственно будет изменена размерность массива.
 
Автор
T

The Dream

Новичок
Сообщения
393
Репутация
3
InnI, подскажите пожалуйста, можно ли с помощью ReDim - уменьшить массив. Если да - то на пустое значение станет предыдущее? Я о удалении строчки и о цветах.
 

InnI

AutoIT Гуру
Сообщения
4,922
Репутация
1,432
The Dream [?]
можно ли с помощью ReDim - уменьшить массив
Можно.
на пустое значение станет предыдущее?
Нет. При изменении размерности в меньшую сторону - будут удалены последние значения:
Код:
#include <Array.au3>
Dim $array[5][3] = [[1,2,3],[4,5,6],[7,8,9],[10,11,12],[13,14,15]]
_ArrayDisplay($array)
ReDim $array[3][2] ; уменьшаем
_ArrayDisplay($array)
ReDim $array[4][4] ; увеличиваем
_ArrayDisplay($array)

Удаление из середины трёхмерного массива - это нужно самому сочинять, т.к. даже функция
Код:
_ArrayDelete()
из Array.au3 работает с двумя измерениями.
 

sims

Осваивающий
Сообщения
184
Репутация
24
В таких случаях используют не массив, а связанный список. Тогда нет необходимости сдвигать данные при удалении элемента или раздвигать их при вставке.
 
Автор
T

The Dream

Новичок
Сообщения
393
Репутация
3
sims

а связанный список это что именно.. ?
 

sims

Осваивающий
Сообщения
184
Репутация
24
http://ru.wikipedia.org/wiki/Связный_список
 
Автор
T

The Dream

Новичок
Сообщения
393
Репутация
3
sims я имел ввиду реализацию на autoit-е вообще-то.. :whistle:
 

AZJIO

Меценат
Меценат
Сообщения
2,879
Репутация
1,194
Связанный список на языке AutoIt3 называется ассоциированный массив.
Включить стиль сортировки (см. в справке) и при добавлении должно автоматически сортироваться, типа раздвигая список.
 

sims

Осваивающий
Сообщения
184
Репутация
24
Ассоциативный массив это немного другое, хоть и можно его применить в данном случае.
В списка нет идентификатора в отличие от массива. http://ru.wikipedia.org/wiki/Ассоциативный_массив
 

AZJIO

Меценат
Меценат
Сообщения
2,879
Репутация
1,194
sims
В принципе ассоциированный Google переводит как связанный, а список, тот же массив. Ну тот способ связанного списка обрабатывать ещё сложнее, ведь к нему надо добавить две колонки "предыдущий элемент" и следующий элемент". Добавить то легко, только обрабатывать в цикле сложно, ведь придётся прогнать весь массив в отдельном цикле, чтобы обнаружить следующее связанное значение, и так каждый шаг цикла. Возможно в C++ это какой то нативный элемент, что-то читал в справке, но в AutoIt3 этого нет. Да и в C++ по той же причине не думаю что этот способ популярный. Выигрываем немножко в краткосрочных операциях, проигрываем намного больше в последующей работе.
 

sims

Осваивающий
Сообщения
184
Репутация
24
AZJIO [?]
В принципе ассоциированный Google переводит как связанный, а список, тот же массив.
Нет это не одно и тоже.


Ну тот способ связанного списка обрабатывать ещё сложнее, ведь к нему надо добавить две колонки "предыдущий элемент" и следующий элемент". Добавить то легко, только обрабатывать в цикле сложно, ведь придётся прогнать весь массив в отдельном цикле
Это не массив, а множество структур (равное числу элементов списка). В каждой структуре кроме данных, хранится указатель на предыдущий и следующий элемент (для двухсвязного списка).

Возможно в C++ это какой то нативный элемент
Связный список есть в нативном виде во многих ЯП (не нужно думать об указателях не предыдущий и следующий элемент, это скрыто и в ЯП есть инструменты для работы со списком). Даже удивительно что его нет в AutoIt.

не думаю что этот способ популярный
Еще как популярный. Его часто используют.
 

AZJIO

Меценат
Меценат
Сообщения
2,879
Репутация
1,194
sims [?]
Еще как популярный. Его часто используют.
Где именно (только без Google). Я не вижу преимуществ. Практически везде требуется доступ к элементу по индексу. Да и чтобы вставить в средину связанного списка придётся найти эту средину, ведь надо с него переписать указатели для нового вставленного элемента. Значит пробежать по всем указателям всё же придётся. Нет у меня желания изучать эту тему, а голословно я не верю. Да и в рамках AutoIt3 следует учитывать 10-кратную потерю времени при интерпретации.
Ну а для The Dream обсуждение этого вообще оффтоп.
 

sims

Осваивающий
Сообщения
184
Репутация
24
Согласен, обсуждение списка в этой теме - оффтоп, но где его начали, там и продолжаем.

AZJIO [?]
ведь надо с него переписать указатели для нового вставленного элемента.
Низкоуровневая работа с указателями на элементы списка, разве что только в Си, где он нативно не реализован (или в других ЯП без нативной поддержки списка).
Обычно в ЯП имеются библиотеки, реализующие список и работу с ним, что избавляет от необходимости работать с указателями. О них можно даже не подозревать.


Практически везде требуется доступ к элементу по индексу. Да и чтобы вставить в средину связанного списка
Допустим что в списке имеются 10 элементов и нужно вставить новый после 4 элемента. (извините, но код не на AutoIt).
Код:
; Создали двухсвязный список.

NewList Test()

; Добавили в него 10 элементов.

For i=1 To 10
  AddElement(Test())
  Test() = i
Next

; Выбрали по индексу 4 элемент (отсчет начинается с нуля).

SelectElement(Test(), 3)

; Вставка нового элемента после выбраного.

AddElement(Test())
И никаких вам указателей на элементы. Они есть, но скрыты от программиста.

следует учитывать 10-кратную потерю времени при интерпретации.
Потеря времени не такая уж и большая. Для примера, добавим в список 10 миллионов элементов и выберем по индексу 5-ти миллионный элемент.
Код:
Procedure.d TimeGet()
  Protected.q f, t
  Protected Result.d
  
  QueryPerformanceFrequency_(@f)
  QueryPerformanceCounter_(@t)  
  
  ProcedureReturn t/f
EndProcedure

; Создали двухсвязный список.

NewList Test()

; Добавили в него 10 миллионов элементов.
For i=1 To 10000000
  AddElement(Test())
Next

FirstElement(Test()) ; Переход в начало списка.

Time.d = TimeGet()
SelectElement(Test(), 5000000)
Debug (TimeGet()-Time)*1000
На это затрачивается всего 10 миллисекунд. Это не много.

Основное преимущество списка в том, что с его элементами можно сделать практически что угодно - добавлять, удалять, перемещать внутри списка или между списками и это будет производится очень быстро, потому что фактически данные не перемещаются, а всего лишь изменяются адреса указателей (но программисту об указателях знать ничего не нужно, это возложено на функции библиотеки ЯП).

Для примера использования списка, представим что пишем игру и нужно рандомно генерировать врагов и их число заранее не известно.
Создадим список с координатами врагов.
Код:
; Структура с координатами врагов.

Structure Pos
  x.l
  y.l
EndStructure

; Список структур.

NewList Enemy.Pos()
Теперь добавим случайное число врагов, расположенных по случайным координатам.
Код:
; Генерируем случайное число врагов (от 2 до 10).

x = Random(10, 2)

; Переход в конец списка.

LastElement(Enemy())

; Добавление новых врагов.

For i=1 To x
  If AddElement(Enemy()) ; Убеждаемся что элемент был добавлен в список.
    ; Устанавливаем случайные координаты врагов.
    Enemy()\x = Random(1000, 20)
    Enemy()\y = Random(1000, 20)
  EndIf
Next
Когда игрок стреляет, проверяем не попала ли пуля во врага (допустим что в переменных x и y текущие координаты пули).
Код:
ForEach Enemy()
  
  If x = Enemy()\x And y = Enemy()\y
    
    ; Пуля попала во врага и он уничтожен, поэтому удаляем элемент, связанный с ним.
    
    DeleteElement(Enemy())
    
    Break ; Выход из цикла.

  EndIf
  
Next
Если это реализовывать с помощью массива, то пришлось бы смещать данные при удалении элемента, а это необоснованная трата времени.
 
Автор
T

The Dream

Новичок
Сообщения
393
Репутация
3
"Согласен, обсуждение списка в этой теме - оффтоп, но где его начали, там и продолжаем."

В purebasic я и сам пользовался таким списком, когда писал много-поточный сервер. Но это другой ЯП. А у нас на форуме - AutoIt. Лучше бы что-то по нему подсказали..
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Помимо того, что отображается в GUI, у элементов списка есть дополнительный параметр, который можно использовать по своему усмотрению. В нативных функциях AutoIt он используется для хранения псевдо ID элемента списка, т.е. то, что возвращает GUIGetMsg(). Если работать со списком с помощью _GUICtrlListView_* функций, то этот параметр можно использовать по своему усмотрению. А для того, чтобы GUIGetMsg() не реагировала на активацию элемента в списке (т.к. значение параметра уже не будет содержать ID элемента, то могут возникнуть ошибки в работе GUI), нужно поставить заглушку для LVN_ITEMACTIVATE, т.е. всегда возвращать 0.

Для работы с параметрами элемента предусмотрены две функции:

_GUICtrlListView_GetItemParam()
_GUICtrlListView_SetItemParam()

Тип параметра - "lparam", т.е. может содержать как целое число, так и 32/64-битный адрес в зависимости от разрядности приложения.

К слову, этот параметр есть у большинства элементов GUI и является незаменимым для таких задач, как, например, сортировка списка.

Как все это использовать применительно к твоей задачи? У тебя есть массив цветов и список. Сейчас каждый элемент списка жестко привязан к определенному индексу в массиве, т.е. элемент списка с индексом 0 соответствует индексу 0 в массиве, 1 - 1, 2 - 2, и т.д. Отсюда и возникают сложности при изменении списка. Все, что тебе нужно, это избавиться от этой привязки. Для этого занеси в параметр элемента списка значение индекса массива, где хранится соответствующий этому элементу цвет и фон. В этом случае отпадает необходимость перелапачивать массив при добавлении или удалении элементов списка. Когда какой-нибудь элемент удаляется из списка, то сначала читается значение его параметра (индекс), обнуляется или каким-либо другим образом помечается соответствующая ячейка в массиве, что будет свидетельствовать о том, что данная ячейка пустая, а затем удаляется сам элемент из списка. Переразмеривать или сортировать массив при этом не нужно. Если нужно добавить новый элемент в список, неважно в какое место, то сначала находится пустая ячейка в массиве. Если таковых нет, то добавляется новая в конец массива (ReDim), а еще лучше добавить сразу 50-100 и пометить их как пустые, чтобы сократить время на эту операцию в дальнейшем. Далее в эту ячейку записываются нужные значения цвета и фона, создается элемент списка и записывается в его параметр индекс соответствующей ячейки в массиве. Как нетрудно догадаться, размер массива будет равен максимальному количеству элементов в списке, которое было за всю работу приложения. Но это не страшно.

Есть еще более изящный и правильный вариант - структуры. К сожалению, в AutoIt структура существует до тех пор, пока существует связанная с ней переменная. Другими словами, для каждой структуры должна быть однозначно объявлена переменная. Так работает диспетчер памяти в AutoIt, а других инструментов для работы с памятью не предоставлено. Но есть WinAPIEx... В этой библиотеке реализован независимый диспетчер памяти. Сначала он имел статус internal и использовался исключительно внутри библиотеки, но в последних версиях был документирован. Преимущества его в том, что он позволяет работать с блоками памяти посредством их указателей. А кроме того, работает немного быстрее нежели механизм структур.

Вот основные функции для работы с памятью:

_WinAPI_CreateBuffer()
_WinAPI_CreateBufferFromStruct()
_WinAPI_CreateString()
_WinAPI_FreeMemory()
_WinAPI_GetMemorySize()
_WinAPI_IsMemory()

Нас интересует только первая или вторая функция. Эти функции создают блок памяти необходимого размера и возвращают его указатель.

Вернемся к нашим баранам. Избавимся полностью от массива и будем хранить значения цветов для каждого элемента списка в отдельной структуре типа "dword;dword". Для выделения блока памяти и записи в него значений пишем так:

Код:
#Include <WinAPIEx.au3>

Global Const $sParam = "dword TextColor;dword BackgroundColor"

; Выделяем блок памяти размером 8 байт
$pParam = _WinAPI_CreateBuffer(8)

; Записываем значения цветов в выделенный блок памяти
$tParam = DllStructCreate($sParam, $pParam)
DllStructSetData($tParam, "TextColor", 1)
DllStructSetData($tParam, "BackgroundColor", 0xFFFFFF)


Теперь переменная $pParam содержит указатель на блок памяти, где находятся нужные нам значения цветов. $pParam не является структурой, и даже если мы обнулим ее, данные не исчезнут из памяти. Если бы $pParam была структурой, то при изменении ее значения, связанная с ней память была бы освобождена, а данные потеряны. Далее как и в первом варианте, только в параметр элемента списка записывается не индекс, а указатель на блок памяти:

Код:
_GUICtrlListView_SetItemParam(..., ..., $pParam)


Получить данные из блока памяти можно следующим образом:

Код:
$pParam = _GUICtrlListView_GetItemParam(..., ...)
$tParam = DllStructCreate($sParam, $pParam)
$iTextColor = DllStructGetData($tParam, "TextColor")
$iBackgroundColor = DllStructGetData($tParam, "BackgroundColor")


ВАЖНО. Чтобы не было утечки памяти необходимо освободить выделенный ранее блок памяти когда он будет ненужен (удаление элемена из списка) с помощью функции _WinAPI_FreeMemory():

Код:
$pParam = _GUICtrlListView_GetItemParam(..., ...)
_WinAPI_FreeMemory($pParam)
_GUICtrlListView_DeleteItem(..., ...)


Если используется функция _GUICtrlListView_DeleteAllItems(), то необходимо предварительно пробежаться по всем элементам списка и освободить соответствующие им блоки памяти.

Как видно, теперь все данные хранятся в самом списке, а необходимость в массиве полностью отпала. Данные о цвете элемента списка жестко привязаны к самому элементу, и любые изменения в списке (добавление, удаление, сортировка и т.д.) никак не повлияют на результат отображения.

P.S.

Ух... Проще было пример набросать чем столько писать.

:smile:
 
Автор
T

The Dream

Новичок
Сообщения
393
Репутация
3
Уважаемый Yashied! Большое Вам спасибо, скоро буду разбираться!

OffTopic:
Если Вам не сложно, скажите - где вы работаете? (интересно было давно уже, но никак не решался спросить)
 

ildar

Осваивающий
Сообщения
252
Репутация
29
Yashied
Отлично все написано, перенести бы эту статейку куда-нибудь на всеобщее обозрение.
 
Верх