Что нового

Смещение индексов пунктов ListView при их удалении

firex

AutoIT Гуру
Сообщения
943
Репутация
208
Доброго времени суток.
Собственно интересует проблема:

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

Это возможно? Заранее спасибо.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
firex [?]
ситуацию осложняют многочисленные массивы в массивах, содержащие информацию добавленную в "кучу"
Массивы не могут усложнять, это делает тот, кто их строил :smile:.

единственный адекватный вариант - убрать это смещение.
Не единственный (хотя я вообще не считаю это вариантом). Нужно просто грамотно построить массив для списка.

В принципе, индекс можно хранить в Param пункта, и обращаться к нему, но имхо, это неправильный подход.
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
CreatoR
Я попробую объяснить.
Есть 2 ListView, один содержит список сокетов, а второй кучу клиентов, которые в разное время подключились к разным сокетам. Массив содержит список сокетов, вторым элементом каждого является массив клиентов с индексами пунктов. Во и как тогда убрать клиента из скажем первого сокета не перемешав всех остальных.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Здесь я описывал похожее решение (даже два).
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
firex [?]
как тогда убрать клиента из скажем первого сокета не перемешав всех остальных
Извлечь массив, удалить нужный индекс, и присвоить обратно во второй элемент массива.
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
CreatoR [?]
Извлечь массив, удалить нужный индекс, и присвоить обратно во второй элемент массива.
Да причем здесь это, вот накидал для вас:
Код:
$_ahListen[1][5] ;[0] Handle; [1] sPort; [2] aClients; [3] LW1_Index
;И сам массив 
$aClients[1][3] ;[0] Handle; [1] sIP; [2] LW2_Index


У нас скажем 50 клиентов на 5 портах, ну удалили мы случайного клиента(принцип как и у вас):
Код:
$_aTmpClients = $_ahListen[$Idx1][2]
$_aTmpClients[0][0] -= 1
_GUICtrlListView_DeleteItem( $_hListView2, $_aTmpClients[$Idx2][2] )
_ArrayDelete( $_aTmpClients[$Idx2] )
$_ahListen[$Idx1][2] = $_aTmpClients


И что нам это дает? Все связанные индексы пунктов "[2] LW2_Index" у всех клиентов больше не могут быть действительными, если мы удалили скажем самого первого клиента.
Вы видимо изначально не поняли того, что я хотел. Да и организация иная невозможна, так как мне не хватит и тысячи слов, что бы описать что там творится дальше.

Yashied
Большое спасибо, завтра поэкспериментирую.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
firex [?]
принцип как и у вас
Не совсем.
В массиве клиентов нужно найти соответствующий индекс, и удалить его, а не элемент массива со значением индекса.

Можно ещё не удалять элемент, а присваивать ему некое Dummy значение, типа -1, ну или пустую строку.
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
CreatoR [?]
Не совсем.В массиве клиентов нужно найти соответствующий индекс, и удалить его, а не элемент массива со значением индекса.Можно ещё не удалять элемент, а присваивать ему некое Dummy значение, типа -1, ну или пустую строку.
Так я ведь и элемент в ListView удаляю. Меня не волнует смещение в самих массивах, там можно все свободно удалять. Если же я удалю пункт в ListView, то все индексы хранящиеся в aClients[$i][2] на всех сокетах становятся не действительны(взял я любого клиента, взял номер пункта в ListView - и он уже будет не тем).

И причем здесь Dummy вообще? Кажется мне, что мы говорим о разных вещах.
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
firex
Может снизишь немного пыл, я тебе вообще то помочь пытаюсь.

firex [?]
Если же я удалю пункт в ListView, то все индексы хранящиеся в aClients[$i][2] на всех сокетах становятся не действительны
Тогда что мешает сместить эти индексы в массиве?
Удаляешь последний элемент массива, а смещаешь (с конца) до удалённого индекса.
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
CreatoR [?]
Может снизишь немного пыл, я тебе вообще то помочь пытаюсь.
Прошу прощения если показался грубым, на самом деле я пытаюсь понять ваши советы, но вот что то думаю, что мы запутались.

Тогда что мешает сместить эти индексы в массиве?Удаляешь последний элемент массива, а смещаешь (с конца) до удалённого индекса.
Это конечно вариант, но когда у нас прослушивается по 5 сокетов, на каждом из которых по 50 клиентов - то это будет выглядеть примерно как "костыль", а в случае удаления всего сокета(со всеми клиентами), смещать в других сокетах придется при удалении каждого клиента в текущем и так 50 раз. Хотелось бы обойтись без этого :smile:
 

AZJIO

Меценат
Меценат
Сообщения
2,879
Репутация
1,194
Достаточно попробовать, можно даже пару вариантов. Первый через Param осуществить связь, второй ну после удаления надо в массиве сокетов иметь связанный триггер. Удалил пункт, помечай в массиве к примеру "-1" тогда при переборе счётчик не будет сбит, так как будет пропускать такие пункты. Но связь через Param кажется надёжней и требует меньше скурпулёзной проверки алгоритма, потому что так она ляжет на Microsoft, иначе на автора программы.
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
AZJIO [?]
помечай в массиве к примеру "-1" тогда при переборе счётчик не будет сбит
Я тоже подумывал над указанием "смещения", но что если был убран клиент из второго сокета, а затем скажем из третьего. Вот для наглядного примера: (сокет|клиент|lw_idx)

(подключаются в разнобой)
1|1|0
1|2|1
2|3|2
1|4|3

К примеру нам понадобилось удалить клиента #3 (2|3|2) (и его пункт в ListView).
1|1|0
1|2|1
1|4|3

Теперь для клиента #4 индекс элемента (3) не действителен, так как он равен 2.
Вы скорее всего подумали, что номер ячейки соответствует номеру в ListView. В этом случае ваш вариант подошел бы(нашли -1, поставили оффсет -1) - и так далее, но у нас имеется несколько массивов заполненных в разнобой.

Пока был на учебе придумал кое какой вариант, сейчас попробую осуществить, если не получится, то займусь Param.

А именно( тот костыль, что я не хотел делать ):
Обход всех клиентов на всех сокетах с правкой индекса при удалении клиента.



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

Остановился пока на этих вариантах, если будут слишком сильно нагружать, то придется использовать пример Yashied'а. Всем спасибо.

Код:
Func _DelSocket( $_iSocket )
	Local $_iClSock, $_aClients, $Idx, $_iLW_Idx
	; ---
	If $_iSocket >= 0 Then
		If $_ahListen[$_iSocket][0] Then
			TCPCloseSocket( $_ahListen[$_iSocket][1] )
			; ---
			$_iLW_Idx = $_ahListen[$_iSocket][4]
			$_aClients = $_ahListen[$_iSocket][3]
			For $_iClSock = 1 To $_aClients[0][0] Step 1
				_RemoveUser( $_iSocket, 1, -1 )
			Next
		EndIf

		$_ahListen[0][0] -= 1
		_ArrayDelete( $_ahListen, $_iSocket )
		For $Idx = 1 To $_ahListen[0][0] - 1 Step 1
			If $_ahListen[$Idx][4] > $_iLW_Idx Then
				$_ahListen[$Idx][4] -= 1
			EndIf
		Next
	Else
		_ConsolePrint( 'Cannot delete socket[' & $_iSocket & ']' )
		Return False
	EndIf

	Return True
EndFunc

Func _RemoveUser( $_iSock, $_iClSock, $_iReason = -1 )
	Local $_aClients = $_ahListen[$_iSock][3], $_iLW_Idx
	; ---
	If IsArray( $_aClients ) And $_iClSock <= $_aClients[0][0] Then
		If $_iReason >= 0 Then _
			_ConsolePrint( 'Remove user [' & $_aClients[$_iClSock][1] & '], reason: ' & $_iReason )

		TCPCloseSocket( $_aClients[$_iClSock][0] )
		; ---
		$_aClients[0][0] -= 1
		$_iLW_Idx = $_aClients[$_iClSock][3] ;Client item in LW
		_ArrayDelete( $_aClients, $_iClSock )

		_GUICtrlListView_DeleteItem( $_hMain_ListView[0 ], $_iLW_Idx )
		$_ahListen[$_iSock][3] = $_aClients ;Saving new aClients
		; ---
		For $_iSock = 1 To $_ahListen[0][0] - 1 Step 1
			$_aClients = $_ahListen[$_iSock][3]
			For $_iClSock = 1 To $_aClients[0][0] Step 1
				If $_aClients[$_iClSock][3] > $_iLW_Idx Then
					$_aClients[$_iClSock][3] -= 1
					$_ahListen[$_iSock][3] = $_aClients
				EndIf
			Next
		Next
		Return True
	EndIf

	Return SetError( 1, 0, False )
EndFunc
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
firex [?]
К примеру нам понадобилось удалить клиента #3 (2|3|2) (и его пункт в ListView).
1|1|0
1|2|1
1|4|3
Теперь для клиента #4 индекс элемента (3) не действителен, так как он равен 2.
Не удалить, а присвоить -1 (об этом Dummy я и говорил выше):

Код:
1|1|0
1|2|1
1|3|-1
1|4|3
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
CreatoR
А, ну так вот теперь я вас понял. Только все равно не то: при удалении клиента мы лишь знаем порт, на котором он находится и номер среди клиентов на этом порте. Если мы и сделаем Dummy удалив элемент в ListView и этим самым сделаем обход проблемы, то только разве для текущего порта с его клиентами (вот расширенное представление для того, что бы было понятно):

1|1|0
1|2|1
2|3|2
1|4|3
Прошло немного времени, мы удалили 1|4|3 ( стало 1|4|-1 )
Далее подключаются клиенты:
3|5|3
1|6|4
2|7|5

В памяти это расположено в массивах:
1|1|0
1|2|1
1|4|-1
1|6|4

2|3|2
2|7|5

3|5|3

1) Удаляем связанный пункт в LW и пользователя #2 из 1 сокета и #1 из 2 сокета:
1|1|0
1|2|-1 ;Removed
1|4|-1
1|6|4

2|3|-1 ;Removed
2|7|5

3|5|3

2) По триггеру из ListView(к примеру дабл клик) - мы получаем индекс выделенного пункта - 5, сможете ли вы определить клиента, который связан с этим пунктом? Было бы все линейно - определить бы удалось, но у нас тут все в "перемешку".
 

CreatoR

Must AutoIt!
Команда форума
Администратор
Сообщения
8,671
Репутация
2,481
firex [?]
определить бы удалось, но у нас тут все в "перемешку"
Мне если честно трудно понять, почему там должно быть всё вперемешку.
Можно же построить массив таким образом, чтобы сохранялось соответствие, даже в подмассиве, и даже после удаления.
Просто я считаю, нужно побольше приложить усилий для этой задачи, это всё вполне выполнимо.

Мне трудно показать это на примере, т.к не вижу всей картины.
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
CreatoR [?]
Мне если честно трудно понять, почему там должно быть всё вперемешку.Можно же построить массив таким образом, чтобы сохранялось соответствие, даже в подмассиве, и даже после удаления.Просто я считаю, нужно побольше приложить усилий для этой задачи, это всё вполне выполнимо.Мне трудно показать это на примере, т.к не вижу всей картины.
Соответствие в массиве не нарушается, нарушается соответствие некоторых данных в массиве с пунктами ListView. Для наглядности некоторые вырезки организации подключения и его обработка (текущая организация обходит все проблемы, но выглядит это как "костыль"):
(вызов всех приложенных функций происходит в любой момент в любой последовательности), неужели тут имеет место другая организация? На мой взгляд этот вариант наиболее аккуратен и интуитивно понятен :smile:

Код:
Global $_ahListen[10][5] = [[1,9]] ;State|Handle|Port|Clients|LW_Item

While 1
    $_hMsg = GUIGetMsg(1)
    Switch $_hMsg[1]
        ...
	EndSwitch
	; ---
	For $_SockIdx = 1 To $_ahListen[0][0]-1 Step 1
		If $_ahListen[$_SockIdx][0] Then ;Valid
			$_hTmpSock = TCPAccept( $_ahListen[$_SockIdx][1] )
			$_aSockClients = $_ahListen[$_SockIdx][3]
			If $_hTmpSock <> -1 Then ;New user REGION
				$_aSockClients[0][0] += 1 ;aClients

				$_uB = $_aSockClients[0][0]
				ReDim $_aSockClients[$_uB+1][4]

				$_aSockClients[$_uB][0] = $_hTmpSock ;hSocket
				$_aSockClients[$_uB][1] = _SocketToIP($_hTmpSock) ;sIP
				$_aSockClients[$_uB][2] = '' ;Callback_onRecv
				$_aSockClients[$_uB][3] = -1 ;ListView ITEM / Задается в OnUserConnect
				; ---
				$_ahListen[$_SockIdx][3] = $_aSockClients
				$_hTmpSock = -1
				; ---
				_Callback_OnUserConnect( $_SockIdx, $_uB ) ;Создаем пункт в ListView
			EndIf
			; ---
			For $_ClSockIdx = 1 To $_aSockClients[0][0] Step 1
				$_vRecv = _PackageRecv( $_aSockClients[$_ClSockIdx][0] )
				If @Error Then
					; 1 = Not responsed | connection failure
					; 2 = Bad package | connection interrupt
					; 3 = Not !Success | response failure
					_RemoveUser( $_SockIdx, $_ClSockIdx, @Error )
					If Not @Error Then _
						ExitLoop ;User removed
				ElseIf IsArray( $_vRecv ) Then
					; 0 = Challenge name
					; 1 = Unpacked vData(!Not Array), Array = Packed
					; 2 = vData Flag(Data type or other)
					; 3 = Error number(integer)
					Call( $_aSockClients[$_ClSockIdx][2], $_SockIdx, $_ClSockIdx, $_vRecv )
				EndIf
			Next
		EndIf
	Next
WEnd

Func _AddSocket( $_sPort, $_vUpNP = Default )
	Local $_hSocket = TCPListen( @IPAddress1, Int( $_sPort ) )
	Local $_aClients[1][4] = [[0]], $Idx = $_ahListen[0][0]
	Local $_sMessage = 'Listening', $_bState = 1
	; ---
	If @Error Then
		$_sMessage = 'WSA Error! Code: ' & @Error
		$_bState = 0
	EndIf
	; ---
	If $Idx <= $_ahListen[0][1] Then
		$_ahListen[$Idx][0] = $_bState
		$_ahListen[$Idx][1] = $_hSocket
		$_ahListen[$Idx][2] = $_sPort
		$_ahListen[$Idx][3] = $_aClients
		; ---
		$_ahListen[$Idx][4] = _GUICtrlListView_AddItem( $_hMain_ListView[3 ], $_bState, -1 )
		_GUICtrlListView_AddSubItem( $_hMain_ListView[3 ], $Idx-1, $_hSocket, 1)
		_GUICtrlListView_AddSubItem( $_hMain_ListView[3 ], $Idx-1, $_sPort, 2)
		_GUICtrlListView_AddSubItem( $_hMain_ListView[3 ], $Idx-1, $_sMessage, 3)
		_GUICtrlListView_AddSubItem( $_hMain_ListView[3 ], $Idx-1, $_vUpNP, 4)
		; ---
		$_ahListen[0][0] += 1
	EndIf
EndFunc

Func _RemoveUser( $_iSock, $_iClSock, $_iReason = -1 )
	Local $_aClients = $_ahListen[$_iSock][3], $_iLW_Idx
	; ---
	If IsArray( $_aClients ) And $_iClSock <= $_aClients[0][0] Then
		If $_iReason >= 0 Then _
			_ConsolePrint( 'Remove user [' & $_aClients[$_iClSock][1] & '], reason: ' & $_iReason )

		TCPCloseSocket( $_aClients[$_iClSock][0] )
		; ---
		$_aClients[0][0] -= 1
		$_iLW_Idx = $_aClients[$_iClSock][3] ;Client item in LW
		_ArrayDelete( $_aClients, $_iClSock )

		_GUICtrlListView_DeleteItem( GUICtrlGetHandle( $_hMain_ListView[0 ] ), $_iLW_Idx )
		$_ahListen[$_iSock][3] = $_aClients ;Saving new aClients
		; ---
		For $_iSock = 1 To $_ahListen[0][0] - 1 Step 1
			$_aClients = $_ahListen[$_iSock][3]
			For $_iClSock = 1 To $_aClients[0][0] Step 1
				If $_aClients[$_iClSock][3] > $_iLW_Idx Then
					$_aClients[$_iClSock][3] -= 1
					$_ahListen[$_iSock][3] = $_aClients
				EndIf
			Next
		Next
		Return True
	EndIf

	Return SetError( 1, 0, False )
EndFunc

Func _DelSocket( $_iSocket )
	Local $_iClSock, $_aClients, $Idx, $_iLW_Idx
	; ---
	If $_iSocket > 0 Then
		If $_ahListen[$_iSocket][0] Then
			TCPCloseSocket( $_ahListen[$_iSocket][1] )
			; ---
			$_aClients = $_ahListen[$_iSocket][3]
			For $_iClSock = 1 To $_aClients[0][0] Step 1
				_RemoveUser( $_iSocket, 1, -1 ) ;ReDim, use only first element
			Next
		EndIf
		$_iLW_Idx = $_ahListen[$_iSocket][4]
		_GUICtrlListView_DeleteItem( GUICtrlGetHandle( $_hMain_ListView[3 ] ), $_iLW_Idx )

		$_ahListen[0][0] -= 1
		_ArrayDelete( $_ahListen, $_iSocket )
		For $Idx = 1 To $_ahListen[0][0] - 1 Step 1
			If $_ahListen[$Idx][4] > $_iLW_Idx Then
				$_ahListen[$Idx][4] -= 1
			EndIf
		Next
	Else
		_ConsolePrint( 'Cannot delete socket[' & $_iSocket & ']!' )
		Return False
	EndIf

	Return True
EndFunc


Если у вас есть идеи другой организации, то я их с удовольствием учту, но она не должна вызывать "остановок" в работе.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
firex, эта тема уже превращается во флуд. Вместо того, чтобы просто описать суть проблемы на простом примере, вы с каждым новым сообщением только вносите путаницу, вдаваясь в детали работы вашей программы. Причем здесь клиенты? Причем здесь порты? Причем здесь, в конце концов, какие-то подмассивы? Вопрос должен был звучать так:

Есть ListView. Есть массив, в котором содержатся дополнительные данные для каждого элемента ListView. Индекс каждого элемента соответствует индексу массива с соответствующими данными. При удалении произвольного элемента ListView теряется соответствие между индексами ListView и массива с данными, т.к. индексы ListView изменяются после удаления из него какого-либо элемента. Как решить эту проблему?

Ответ:

Вариант 1

После удаления элемента ListView удалите соответствующую ему строку в массиве, тем самым восстановив соответствие индексов.

Вариант 2

Избавьтесь от жесткой привязки индексов ListView к индексам массива. Специально для этих целей придуман параметр "Param". При добавлении элемента ListView запишите в "Param" индекс ячейки массива, в которой находятся соответствующие данные. Таким образом теперь можно даже сортировать ListView, т.к. жесткой связи нет. Для получения нужных данных достаточно просто прочитать значение из "Param" (индекс массива) и получить соответствующие данные из массива. Просто и элегантно.

Нюансы.

Из массива нельзя удалять строки, т.к. индексы изменятся. Поэтому, если предполагается частое добавление и удаление элементов ListView, то придется написать небольшую вспомогательную функцию (обработчик). При удалении элемента ListView необходимо в соответствующую ячейку массива (индекс берется из "Param" удаляемого элемента ListView) какое-нибудь значение (CreatoR назвал это Dummy), например (-1, Default и т.д.), которое бы свидетельствало, что данная ячейка пустая. При добавлении элемента ListView сначала нужно пробежаться по массиву и найти первую пустую ячейку. Если таковых нет, то делается ReDim, помечаются все новые ячейки как пустые и берется первая свободная ячейка. После этого в эту ячейку (строку) записываются все необходимые данные, а ее индекс записывается в "Param" нового элемента ListView. Для максимальной производительности рекомендую сразу создать массив, например, из 100 элементов и пометить их все как пустые. Когда они все заполнятся, то делать ReDim еще на 100 элементов и т.д.

Вариант 3

Аналогичен второму варианту с той лишь разницей, что вместо массива используются структуры. В этом случае отпадает необходимость писать какой-либо обработчик и прочие прелести работы с массивами. В "Param" записывается указатель на блок памяти, в котором находятся соответствующие этому элементу ListView данные. Это, IMHO, самый правильный вариант. Более подробно об этом можно узнать по ссылке, которую я дал выше.
 

Z_Lenar

Продвинутый
Сообщения
209
Репутация
52
А если создать поверх оригинального(скрытого) ListView еще один содержащий ссылки на первый? Т.е. как-бы закрыть его созданием второго ListView с размерами и координатами первого? В исходном ListView менять какой-то столбец добавлением/вычетом '-'.
Оптимальный вариант по-моему создать массив содержащий не визуализированные данные, а ListView поверх него изменять в соответствии скрытия/показа элементов.
Ну в крайнем случае написать собственный контрол или использовать БД с поддержкой триггеров.
 
Автор
firex

firex

AutoIT Гуру
Сообщения
943
Репутация
208
Yashied
Поступлю по вашему 3 варианту, лучше всего будет хранить все данные в Param, а от массивов отказаться.
 

Yashied

Модератор
Команда форума
Глобальный модератор
Сообщения
5,379
Репутация
2,724
Последующие сообщения удалил, т.к. они не касаются данной темы.
 
Верх