Сейчас на форуме: (+2 невидимых)

 eXeL@B —› Оффтоп —› Как организовать синхронизацию сортированных записей в TList со списком в TListView?
Посл.ответ Сообщение


Ранг: 450.3 (мудрец), 13thx
Активность: 0.20
Статус: Участник

Создано: 11 ноября 2009 02:43
· Личное сообщение · #1

Пишу небольшую серверную часть которая должна принимать от клиентов некое число и отображать всех подключенных в ListView табличке в отсортированном виде. Все коннекты хранятся в списке который сортируется и обновляется по мере отключения, подключения и передачи данных клиентами. Всё работает сносно, но очень не нравится мне синхронизация сортированных записей в TList с TListView. Сделал как сумел, но чувствую есть способы грамотнее и красивее. Очень прошу подсказать как реализовать всё это без постоянной очистки и регенерации ListView. Сейчас по таймеру вызываю Refresh...
Вот код:

Code:
  1. type
  2.   PConnects = ^TConnects;
  3.   TConnects = record
  4.     IP: ShortString;
  5.     ThreadID: Cardinal;
  6.     Connection: TidTCPServerConnection;
  7.     Info: shortstring;
  8. end;
  9.  
  10.  
  11. var conn: TList;
  12.  
  13. procedure TForm1.FormCreate(Sender: TObject);
  14. begin
  15. conn:=TList.Create; // Создаём список
  16. IdTCPServer1.Active:=true;
  17. End;
  18.  
  19. procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
  20. var ConAux: PConnects;
  21. begin
  22.      GetMem(ConAux,SizeOf(TConnects));
  23.        ConAux.ThreadID:= AThread.ThreadID;
  24.        ConAux.Connection:= AThread.Connection;
  25.        ConAux.IP:= AThread.Connection.Socket.Binding.PeerIP;
  26.        AThread.Data:= TObject(ConAux);
  27.        conn.Add(ConAux); // Добавляем в список
  28. End;
  29.  
  30. procedure TForm1.IdTCPServer1Disconnect(AThread: TIdPeerThread);
  31. var ConAux: PConnects;
  32. begin
  33.     ConAux:= PConnects(AThread.Data);
  34.     try
  35.       conn.Remove(ConAux); // Удаляем из списка
  36.       AThread.Data:= nil;
  37.     finally
  38.       FreeMem(ConAux);
  39.     end;
  40. end;
  41.  
  42. procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
  43. var ConAux:PConnects;
  44. begin
  45. ConAux:=PConnects(AThread.Data);
  46. ConAux.Connection.ReadBuffer(x,4);
  47. ConAux.Info:=inttostr(x); // Строка переданная клиентом. По ней сортировка.
  48. end;
  49.  
  50. // Сортировка по Info
  51. function CompareNames(Item1, Item2: Pointer): Integer;
  52. begin
  53.    Result:= CompareText(TConnects(Item1^).Info,TConnects(Item2^).Info);
  54. end;
  55.  
  56. Procedure Refesh();
  57. var  it:TListItem;
  58.       i:integer;
  59.  ConAux:PConnects;
  60. begin
  61. conn.Sort(@CompareNames); // Сортировали список
  62. form1.ListView1.Clear; // Отчистили ListView
  63.   for i:=0 to conn.Count-1 do
  64.   begin
  65.      try
  66.        ConAux:= PConnects(conn[i]);
  67.        it:=form1.listview1.Items.Add; // Добавили строку
  68.        it.caption:=ConAux.IP+' '+ConAux.Info; // Записали IP и значение
  69.      finally
  70.      end;
  71.   end;
  72. end;




Ранг: 512.7 (!), 360thx
Активность: 0.270.04
Статус: Модератор

Создано: 11 ноября 2009 03:22 · Поправил: sendersu
· Личное сообщение · #2

Несколько замечаний
1) зачем таймер? да и не безопасно так делать (у таймера ведь свой поток?)
2) предложение - вызивать treeview Refresh() после апдейта списка conn в методах OnConnect()/OnDisconnect()
3) в самом Refresh - есть варанты:

а) очистка/заполнение - как у тебя, но я б добавил вызов BeginUpdate()/EndUpdate() на form1.listview1.Items (глянь хелп зачем)
б) ребилд листа без варианта а - надо пробежаться по айтемах дерева, что пропало - удалить, что появилось в листе - добавить. Begin/EndUpdate() тоже надо

4) зачем try/finally в цикле в Refresh?
5) идея - добавить иконку в дерево для каждого елеме. зеленая - есть конект, красная - конец/оборвался (на следующем апдейте дерева - удалять)

P.S. под деревом (treeview) надо читать listview, сорри




Ранг: 450.3 (мудрец), 13thx
Активность: 0.20
Статус: Участник

Создано: 11 ноября 2009 14:40 · Поправил: ToBad
· Личное сообщение · #3

sendersu пишет:
1) зачем таймер? да и не безопасно так делать (у таймера ведь свой поток?)


Данные постоянно меняются у клиентов, но по протоколу они передают их только по запросу сервера. По этому таймер нужен, ровно как и внеплановый опрос по кнопке будет присутствовать. Почему не безопасно? Тут насколько я понимаю и так сервером создаются потоки для каждого клиента...

sendersu пишет:
но я б добавил вызов BeginUpdate()/EndUpdate()


Отличный совет!!! Я даже не знал об этих методах....

sendersu пишет:
предложение - вызывать treeview Refresh() после апдейта списка conn в методах OnConnect()/OnDisconnect()


Да, так и делаю, просто выкинул лишнее для наглядности когда постил.

sendersu пишет:
ребилд листа без варианта а - надо пробежаться по айтемах дерева, что пропало - удалить, что появилось в листе - добавить. Begin/EndUpdate() тоже надо


Не совсем понял, ещё один Begin/EndUpdate()? На TList что-ли который Conn?

sendersu пишет:
зачем try/finally в цикле в Refresh?


Х.з. try/except поставлю, в момент прохода по листу может дисконнект одного из клиентов произойти...



Ранг: 512.7 (!), 360thx
Активность: 0.270.04
Статус: Модератор

Создано: 11 ноября 2009 15:28
· Личное сообщение · #4

ToBad пишет:
Данные постоянно меняются у клиентов, но по протоколу они передают их только по запросу сервера. По этому таймер нужен, ровно как и внеплановый опрос по кнопке будет присутствовать. Почему не безопасно? Тут насколько я понимаю и так сервером создаются потоки для каждого клиента...

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

ToBad пишет:
sendersu пишет:ребилд листа без варианта а - надо пробежаться по айтемах дерева, что пропало - удалить, что появилось в листе - добавить. Begin/EndUpdate() тоже надо Не совсем понял, ещё один Begin/EndUpdate()? На TList что-ли который Conn?


имел в виду то же что и в 3а пункте.


Х.з. try/except поставлю, в момент прохода по листу может дисконнект одного из клиентов произойти...

дык в етом и весь нюанс, ситуация: мы ребилдим список, и в етот классный момент у нас происходит доваление новых данных -> здесь возможно все, от креша до неправильного отображения данных
поетому, следует подумать о блокировке общего ресурса, а именно - списка Conn
методов -много




Ранг: 450.3 (мудрец), 13thx
Активность: 0.20
Статус: Участник

Создано: 11 ноября 2009 17:10
· Личное сообщение · #5

sendersu пишет:
То есть список не только для показа активых конектов но и еще атрибутов взаимодействия?


Интересуют только активные коннекты.

sendersu пишет:
дык в етом и весь нюанс, ситуация: мы ребилдим список, и в етот классный момент у нас происходит доваление новых данных -> здесь возможно все, от креша до неправильного отображения данных
поетому, следует подумать о блокировке общего ресурса, а именно - списка Conn
методов -много


Да, это может стать проблемой. Буду придерживать флажками убиение элемента списка при дисконнекте и добавление при коннекте если происходит refresh а так же придерживать refresh при входе в коннект/дисконнект. Так наверное?

По ходу появился ещё вопрос. Сейчас у меня по протоколу есть только возвращение числа клиентом. Существует функция Reask, которая посылает каждому клиенту команду. Послали 5 - клиент вернул свое число. Теперь я добавляю команду 4, получив её клиент инкрементирует у себя число и пришлёт его в ответ. В TConnects добавил Cmd. Если не установлено в 4, то принимает значение 5. Я обрабатываю клики на листвьюв так:
Code:
  1. procedure TForm1.ListView1Click(Sender: TObject);
  2. var ConAux:PConnects;
  3. begin
  4. if ListView1.ItemIndex<>-1 then begin
  5. ConAux:=PConnects(conn[ListView1.ItemIndex]);
  6. showmessage('К посылке команды всё готово');
  7. ConAux.cmd:=4; // Установить команду инкримента.
  8. end;
  9. end;


Когда будет расширен функционал вместо месседжа будет диалоговое окно. В данном примере вывод окна сделан для того, что бы можно было убить клиента и только потом послать команду. Когда клиент жив, при следующем Reask я отправляю 4 и получаю ответ, всё происходит нормально, но если клиента закрыть, смещение индексов в списке мне не страшно, так как я запомнил не индекс перед входом в месседж или диалог, а ConAux нужного мне элемента, однако я не могу сделать так:
Code:
  1. if (ConAux.Connection=nil) or (ConAux=nil) then showmessage('error connection closed');

а следовательно и знать, что выставляю ConAux.cmd:=4; для уже мертвого соединения...




Ранг: 2014.5 (!!!!), 1278thx
Активность: 1.340.25
Статус: Модератор
retired

Создано: 11 ноября 2009 18:11
· Личное сообщение · #6

При чём здесь вообще низкоуровневое и околокрекинговое программирование? Ты создаёшь кучу топиков по кодингу, половина из которых под тематику не попадает вообще никаким боком, заканчивай уже.



Ранг: 101.0 (ветеран), 344thx
Активность: 1.150
Статус: Участник

Создано: 11 ноября 2009 18:19 · Поправил: Модератор
· Личное сообщение · #7

По многочисленным жалобам (обойдёмся без ников) тему переношу. В следующий раз такое лучше создавать в оффтопе.



Ранг: 512.7 (!), 360thx
Активность: 0.270.04
Статус: Модератор

Создано: 12 ноября 2009 02:45
· Личное сообщение · #8

ToBad пишет:
Да, это может стать проблемой. Буду придерживать флажками убиение элемента списка при дисконнекте и добавление при коннекте если происходит refresh а так же придерживать refresh при входе в коннект/дисконнект. Так наверное?


я советую не играться в кошки-мышки а использовать обекты синхронизации ОС - или юзер или кернел мода.
Для примера можно заюзать TCriticalSection клас. Создать 1 обект, перед изменением состояния общего ресурса (список) - захват (Acquire()), после работы - отдать назад (Release())

ToBad пишет:
По ходу появился ещё вопрос.

к индексам желательно не привязиваться (если гуи сортирует - тем более нельзя! индекс плавает, верно?
поетому вот ето -
ConAux:=PConnects(conn[ListView1.ItemIndex]
не есть хорошо, взамен надо запоминать в твоем ListView1 не только Caption, но и сам ConAux (Указатель на структуру клиента), как? вот так:
привязиваемся к реальным обьектам юзера - ConAux типа поинтер на структуру TConnects

ConAux:= PConnects(conn[i]);
it:=form1.listview1.Items.Add; // Добавили строку
it.Data := TObject(ConAux); // <-- теперь у нас есть уникальный ключ для каждого рядка в списке!
...

кстати - утебя уже был похожий подход с потоком - AThread.Data:= TObject(ConAux);
Вообще скажу тебе - в Дельфи такой дженерик подход - пихать свои юзер данные (как правило поинтер на чего то свое в такие вот проперти - Data, Tag, ... (називаються по разному в разных дельфи классах)


по вопросу - все верно, когда клиет умер - его ConAux уже уничтожен (в твоем методе IdTCPServer1Disconnect)

но! когда ты читаеш пропертю Data текущего выбранного елемента ты етого еще не знаешь (поинтер остался, а память под ним может быть уже тю-тю (FreeMem)
чтоб проверить жив ли клиент - можно например поискать его в списке клиентов, а-ля

if conn.IndexOf(currentConAux) <> -1 then
//ура клиент жив!

не забываем, что даже простой поиск по списку через IndexOf() - ето доступ к общему ресурсу, который надо защитить (см. выше)

удачи




Ранг: 450.3 (мудрец), 13thx
Активность: 0.20
Статус: Участник

Создано: 12 ноября 2009 03:36
· Личное сообщение · #9

sendersu - Огромное спасибо за исчерпывающий ответ!!! Ты мне очень помог, теперь есть масса информации для размышления и варианты как это сделать!

progopis пишет:
По многочисленным жалобам (обойдёмся без ников)


p.s. Топик закрываю дабы не сердить уважаемых модераторов и всех остальных многочисленных которые обходятся без ников, но очень не безразличны к порядку на форуме. ;)
Спасибо, что дали дорешить проблему и не забанили по ip весь мой регион, не изгнали с позором с форума и не набили морду...
Впредь буду тщательнее выбирать куда постить...


 eXeL@B —› Оффтоп —› Как организовать синхронизацию сортированных записей в TList со списком в TListView?

У вас должно быть 20 пунктов ранга, чтобы оставлять сообщения в этом подфоруме, но у вас только 0

   Для печати Для печати