powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / TTreeView и RDP
22 сообщений из 22, страница 1 из 1
TTreeView и RDP
    #40004244
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Есть форма, на которой лежит TTreeView с узлами.

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

При подключении в приложение приходит сообщение WM_SYSCOLORCHANGE, которое трансформируется в CM_SYSCOLORCHANGE и рассылается всем контролам.

TTreeView это сообщение обрабатывает так
Код: pascal
1.
2.
3.
4.
5.
6.
procedure TCustomTreeView.CMSysColorChange(var Message: TMessage);
begin
  inherited;
  if not (csLoading in ComponentState) then
    RecreateWnd;
end;

потом попадаем в TWinControl
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
procedure TWinControl.RecreateWnd;
begin
  if WindowHandle <> 0 then Perform(CM_RECREATEWND, 0, 0);
end;

procedure TWinControl.CMRecreateWnd(var Message: TMessage);
var
  WasFocused: Boolean;
begin
  WasFocused := Focused;
  UpdateRecreatingFlag(True);
  try
    DestroyHandle;
    UpdateControlState;
  finally
    UpdateRecreatingFlag(False);
  end;
  if WasFocused and (WindowHandle <> 0) then
    Winapi.Windows.SetFocus(WindowHandle);
end;

а потом в TTreeView
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
procedure TCustomTreeView.CreateWnd;
var
  I: Integer;
begin
  FStateChanging := False;
  inherited CreateWnd;
  TreeView_SetBkColor(Handle, ColorToRGB(Color));
  TreeView_SetTextColor(Handle, ColorToRGB(Font.Color));
  if FMemStream <> nil then
  begin
    Items.BeginUpdate;
    try
      Items.ReadNodeData(FMemStream);
      Items.ReadExpandedState(FMemStream);
      FreeAndNil(FMemStream);
      SetTopItem(Items.GetNodeFromIndex(FSaveTopIndex));
      FSaveTopIndex := 0;
      if FSaveIndexes <> nil then
      begin
        for I := 0 to FSaveIndexes.Count - 1 do
          FSelections.Add(Items.GetNodeFromIndex(Integer(FSaveIndexes[I])));
        FreeAndNil(FSaveIndexes);
        ValidateSelection;
        SetSelected(Selections[0]);
      end
      else
      if FSaveIndex <> -1 then
        SetSelected(Items.GetNodeFromIndex(FSaveIndex));
      FSaveIndex := -1;
    finally
      Items.EndUpdate;
    end;
  end;
  if FSaveIndent <> -1 then
    Indent := FSaveIndent;
  if (Images <> nil) and Images.HandleAllocated then
    SetImageList(Images.Handle, TVSIL_NORMAL);
  if (StateImages <> nil) and StateImages.HandleAllocated then
    SetImageList(StateImages.Handle, TVSIL_STATE);
  if StyleServices.Enabled and TOSVersion.Check(6) and StyleServices.IsSystemStyle then
    SetWindowTheme(Handle, 'explorer', nil); // do not localize
end;

procedure TCustomTreeView.DestroyWnd;
var
  Node: TTreeNode;
  I: Integer;
begin
  FStateChanging := True;
  FRClickNode := nil;
  FShiftAnchor := nil;
  if FCreateWndRestores and (Items.Count > 0) and (csRecreating in ControlState) then
  begin
    FMemStream := TMemoryStream.Create;
    Items.WriteNodeData(FMemStream);
    Items.WriteExpandedState(FMemStream);
    FMemStream.Position := 0;
    FSaveTopIndex := 0;
    FSaveIndex := -1;
    Node := GetTopItem;
    if Node <> nil then
      FSaveTopIndex := Node.AbsoluteIndex;
    Items.BeginUpdate;
    try
      if MultiSelect and (FSelections.Count > 1) then
      begin
        FSaveIndexes := TList.Create;
        for I := 0 to FSelections.Count - 1 do
{$IFDEF CLR}
          FSaveIndexes.Add(TObject(TTreeNode(FSelections[I]).AbsoluteIndex));
{$ELSE}
          FSaveIndexes.Add(Pointer(TTreeNode(FSelections[I]).AbsoluteIndex));
{$ENDIF}
        FSelections.Clear;
      end
      else
      begin
        Node := Selected;
        if Node <> nil then
          FSaveIndex := Node.AbsoluteIndex;
      end;
      Items.Clear;
    finally
      Items.EndUpdate;
    end;
  end;
  FSaveIndent := Indent;
  inherited DestroyWnd;
end;

где видно, что при установленном флаге csRecreating сохраняется текущее состояние дерева, потом все узлы уничтожаются, а потом создаются опять с восстановлением структуры.

Вопрос: уничтожение всех TTreeNode, а потом создание их заново это адекватное решение? Нигде в VCL мне не попадалось уничтожение объектов при уничтожении хендла окна.

Проблема в том, что у меня объекты TTreeNode закешированы в приложении и после переподключения по RDP у меня начинают лезть AV. Сейчас буду смотреть как можно обновить сохраненные объекты

С уважением, Vasilisk
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004248
Соколинский Борис
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_
Вопрос: уничтожение всех TTreeNode, а потом создание их заново это адекватное решение?
По смыслу - нет, по факту - это происходит в разных ситуациях.
Кэшировать придется Id-шники узлов а не хендлы.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004265
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Соколинский Борис
Кэшировать придется Id-шники узлов а не хендлы.
А потом на каждый чих делать рекурсивный поиск? Или дергать TreeView_GetItem?

Я так понимаю, что узнать о том, что произошло перепостроение дерева невозможно?
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004267
rgreat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_,

TTreeView - он такой.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004278
Соколинский Борис
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_
Я так понимаю, что узнать о том, что произошло перепостроение дерева невозможно?
Теоретически можно - переобъявить класс и перекрыть RecreateWnd.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004314
white_nigger
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Тоже самое в листвью. Так работает винда. Vcl пришлось городить костыли для обхода
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004318
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
white_nigger
Так работает винда
Смотри, если на форме лежит кнопка и для формы вызывается RecreateWnd объект TButton никто же не уничтожает. Меняется только TButton.Handle. Что мешало здесь сделать аналогично? Пересоздать узлы, а объекты TTreeNode не трогать.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004323
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Соколинский Борис
Кэшировать придется Id-шники узлов
А есть гарантия, что они совпадут после пересоздания? По идее, они как раз гарантировано не совпадут.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004370
Соколинский Борис
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_
Соколинский Борис
Кэшировать придется Id-шники узлов
А есть гарантия, что они совпадут после пересоздания? По идее, они как раз гарантировано не совпадут.

Дык свои надо создавать, а не на чужие надеяться :)
Если неохота заморачиваться, можно в RecreateWnd заменить в кэше handle-ы узлов (или сами TTreeNode, что там у тебя) на индексы, а после унаследованного метода сделать обратную замену.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004453
Fr0sT-Brutal
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
А зачем кешировать объекты, которые по сути есть кишки контрола и которыми он волен распоряжаться как захочет?
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004467
Фотография makhaon
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Напоролся я в свое время на, скажем так, особенности TTreeView в RDP. Как я понял, оно полностью пересоздается во время логина. Соответственно, все старые указатели, IDшники и т п становятся неверны. Долго и нудно искали, пока нашли концы.
Ну вот - так винда работает. Что с неё взять? )
Решением было полностью отвязать данные от гуя. Был грешок - хранили в Data TTreeView листьев данные. И реконструировать дерево каждый раз из кэша при логине.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004515
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
makhaon
Был грешок - хранили в Data TTreeView листьев данные
А в чем криминал? Data восстанавливается при пересоздании

Итак, что сделал:

1) Перекрыть RecreateWnd нельзя, потому что он не виртуальный
2) Перекрывать обработчик CM_RECREATEWND не имеет смысла, т.к. дерево он уничтожает, но создает обратно только если контрол видимый
3) Перекрыл пару CreateWnd/DestroyWnd
4) Т.к. ссылка у меня была двусторонняя, в объекте сохранялся TTreeNode, а в TTreeNode.Data сам объект, то объявил такой интерфейс
Код: pascal
1.
2.
3.
4.
  ITreeNodeSupport = interface
    ['{6D0B30DF-61E2-4C86-9370-8F1C108C5E07}']
    procedure RecreateNode(ANewNode: TTreeNode);
  end;

и реализовал его в каждом классе, который работал с деревом
Код: pascal
1.
2.
3.
4.
procedure TMyClass.RecreateNode(ANewNode: TTreeNode);
begin
  FTreeNode := ANewNode;
end;

ну а теперь банальная реализация дерева
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
type
  TTreeView = class(Vcl.ComCtrls.TTreeView)
  strict private
    procedure UpdateObjects(ASetNode: Boolean);
  protected
    procedure CreateWnd; override;
    procedure DestroyWnd; override;
  end;

implementation

{ TTreeView }

procedure TTreeView.UpdateObjects(ASetNode: Boolean);
const
  CNullNode: TTreeNode = nil;
var
  LNodePtr: ^TTreeNode;
  LNode: TTreeNode;
  LData: TObject;
  LNodeSup: ITreeNodeSupport;
begin
  if ASetNode then
    LNodePtr := @LNode
  else
    LNodePtr := @CNullNode;

  for LNode in Items do begin
    LData := LNode.Data;
    if LData <> nil then begin
      if not LData.GetInterface(ITreeNodeSupport, LNodeSup) then begin
        raise ETreeViewError.CreateFmt(
          'Class %s is not supported interface ITreeNodeSupport',
          [LData.ClassName]
        );
      end;
      LNodeSup.RecreateNode(LNodePtr^);
    end;
  end;
end;

procedure TTreeView.CreateWnd;
begin
  inherited CreateWnd;
  UpdateObjects(True);
end;

procedure TTreeView.DestroyWnd;
begin
  UpdateObjects(False);
  inherited DestroyWnd;
end;

...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004519
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
makhaon
И реконструировать дерево каждый раз из кэша при логине.
По идее при смене темы в Windows дерево аналогично пересоздастся. Так, что логин по RDP это частный случай
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004533
энди
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
интересно у virtualtreeview такая же ситуация?
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004538
rgreat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
энди,

У virtualtreeview свои проблемы. А именно - неоправданная монструозность и чрезмерная кастомизация.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004549
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Забыл самое главное. Т.к. при пересоздании узлов сохраняются не свойства дерева, а значения, записанные в TTreeNode, то нужно быть очень осторожным с прямым вызовом TreeView_SetItem.

Например процедура, которая одновременно меняла ImageIndex, SelectedIndex, Text
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
procedure UpdateNode(ANode: TTreeNode)
var
  LItem: TTVItem;
begin
  LItem.mask := TVIF_IMAGE or TVIF_SELECTEDIMAGE or TVIF_HANDLE or TVIF_TEXT;
  LItem.hItem := ANode.ItemId;
  LItem.pszText := PChar(FText);
  LItem.iImage := FImgIdx;
  LItem.iSelectedImage := FSelIdx;
  TreeView_SetItem(ANode.Handle, LItem);
end

была переписана так
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
procedure UpdateNode(ANode: TTreeNode)
var
  LItem: TTVItem;
begin
  PInteger(@ANode.ImageIndex)^ := FImgIdx;
  PInteger(@ANode.SelectedIndex)^ := FSelIdx;
  PString(@ANode.Text)^ := FText;

  LItem.mask := TVIF_IMAGE or TVIF_SELECTEDIMAGE or TVIF_HANDLE or TVIF_TEXT;
  LItem.hItem := ANode.ItemId;
  LItem.pszText := LPSTR_TEXTCALLBACK;
  LItem.iImage := FImgIdx;
  LItem.iSelectedImage := FSelIdx;
  TreeView_SetItem(ANode.Handle, LItem);
end
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004559
Fr0sT-Brutal
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
rgreat
У virtualtreeview свои проблемы. А именно - неоправданная монструозность и чрезмерная кастомизация.

Первое - следствие второго, зато на нем можно сделать всякие крутые штуки. И монструозность его сильно преувеличена.

По теме - по-прежнему не понимаю, зачем хранить указатели на узлы
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004574
rgreat
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Fr0sT-Brutal
Первое - следствие второго, зато на нем можно сделать всякие крутые штуки.
Не спорю.
Только вот простые, повседневные, шутки на нем делать не удобно.
И монструозность его сильно преувеличена.
1.8 Мега чисто сорцов для для дерева - это содомия.
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004653
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Fr0sT-Brutal
По теме - по-прежнему не понимаю, зачем хранить указатели на узлы
Например, чтобы обновлять их текст и иконки
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004789
Fr0sT-Brutal
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
_Vasilisk_
Например, чтобы обновлять их текст и иконки

Может, легче сделать отображение по коллбэку?
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004802
Фотография _Vasilisk_
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Fr0sT-Brutal
Может, легче сделать отображение по коллбэку?
Может и легче. Но проект огромный, а OnGetText все равно нет
...
Рейтинг: 0 / 0
TTreeView и RDP
    #40004806
Соколинский Борис
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_
Fr0sT-Brutal
Может, легче сделать отображение по коллбэку?
Может и легче. Но проект огромный, а OnGetText все равно нет
Есть OnCustomDraw...
...
Рейтинг: 0 / 0
22 сообщений из 22, страница 1 из 1
Форумы / Delphi [игнор отключен] [закрыт для гостей] / TTreeView и RDP
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]