Как можно синхронизировать состояние проверки нескольких узлов Virtual Tree View? - PullRequest
2 голосов
/ 08 апреля 2011

Мое дерево имеет 2 уровня узлов - это Список контактов дерево стилей.

Моя проблема в том, что я хотел бы проверить каждый контакт во всех «категориях контактов». Вот скриншот моего списка контактов, как он выглядит сейчас (и да, у меня есть разрешение на его публикацию)

Contact List

Как видите, Тодд Хирш отмечен в категории Категория тестирования , но не в Все контакты . Я пытаюсь добиться того, чтобы у контакта был одинаковый проверенный статус во всех категориях.

Пример: я проверяю Тодда Хирша в категории «Тест» - Тодд Хирш автоматически проверяется во всех контактах (и во всех других категориях). Если я проверю Тодда Хирша во всех контактах, он также будет проверен в категории тестирования. Если я сниму флажок с Тоддом Хиршем во всех контактах, он также будет снят с проверки в категории «Тест».

Я попытался сделать это с помощью событий OnChecking VirtualStringtree, циклически проходя по всему дереву для каждого узла в дереве, однако, когда список контактов большой (2000+), он очень медленный, а когда есть более 5000+, это может даже привести к сбою моей программы (приложение перестало работать)

Что вы предлагаете?

Вот код, который я использую, чтобы убедиться, что контакт проверяется только один раз. (Это не то, что я хочу сейчас, но это то, что я использую прямо сейчас.)

////////////////////////////////////////////////////////////////////////////////
/// HasDuplicateChecked
////////////////////////////////////////////////////////////////////////////////
Function HasDuplicateChecked(Node: PVirtualNode): PVirtualNode;
Var
  ParentNode, ChildNode: PVirtualNode;
  I, J: Integer;
Begin

  // IHCW
  Result := Nil;

  // Get the first node of the tree..
  ParentNode := VT.GetFirst;

  // Loop thru the parent nodes.
  for I := 0 to VT.RootNodeCount - 1 do
  begin
    // Get the first child node.
    ChildNode := ParentNode.FirstChild;
    // Loop thru the children..
    for J := 0 to ParentNode.ChildCount - 1 do
    begin
      // If the ChildNode is checked...
      if NodeIsChecked(ChildNode) then
        // And it is NOT the passed node..
        if ChildNode <> Node then
          // but the data matches..
          if GetData(ChildNode).SkypeID = GetData(Node).SkypeID then
          begin
            // Then pass the Childnode as a result, and EXIT!
            Result := ChildNode;
            Exit;
          end;
      // Next child..
      ChildNode := ChildNode.NextSibling;
    end;
    // Next parent...
    ParentNode := ParentNode.NextSibling;
  end;

End;


////////////////////////////////////////////////////////////////////////////////
/// vtSkypeChecking
////////////////////////////////////////////////////////////////////////////////
procedure TSkypeListEventHandler.vtSkypeChecking(Sender: TBaseVirtualTree;
  Node: PVirtualNode; var NewState: TCheckState; var Allowed: Boolean);
Var
  Level: Integer;
  I: Integer;
  Child: PVirtualNode;
begin
  // Allow the checking..
  Allowed := True;
  // Get the Level..
  Level := Sender.GetNodeLevel(Node);

  // If the level is 0 (Category Level)
  if Level = 0 then
  begin
    // And if the Node's Childcount is more than 0
    if Node.ChildCount > 0 then
    Begin
      // Get the first child..
      Child := Node.FirstChild;
      // Loop thru the children..
      for I := 0 to Node.ChildCount - 1 do
      begin
        // Set the checkstate, and go next..
        Child.CheckState := NewState;
        Child := Child.NextSibling;
      end;
    End;
  end;


  // If the level is 1 (User Level)
  if Level = 1 then
  begin
    // and if the Node's parent is not Nil..
    if Node.Parent <> nil then
    begin
      // aaand, if the new state is Unchecked...
      if (NewState = csUncheckedNormal) or (NewState = csUncheckedPressed) then
      begin
        // .. and if the node checkstate is checked..
        if NodeIsChecked(Node) then
        Begin
          // Set the PARENT node's checkstate to Unchecked!
          Node.Parent.CheckState := csUncheckedNormal;
        End;

      end;
      // BUT, if there is a DUPLICATE of the node, screw the above, and
      // forbid the checking!
      if HasDuplicateChecked(Node) <> nil then
        Allowed := False;

    end;
  end;

  // Uncheck all the duplicates.
  UncheckDuplicates;

  // Refresh the Tree
  Sender.Refresh;

end;

Ответы [ 3 ]

5 голосов
/ 08 апреля 2011

Во-первых, OnChecking - это неправильное событие для обработки.Вы хотите OnChecked.OnChecking на самом деле справедливо спросить: "Разрешено ли изменять состояние проверки этого узла?"Это не , предназначенное для отключения и проверки других узлов.Для этого используйте OnChecked.

Во-вторых, вам не нужно обрабатывать состояние проверки узлов категории.Включите параметр toAutoTristateTracking, и элемент управления автоматически настроит состояния всех связанных дочерних и родительских узлов.(Измените родителя, и все дочерние элементы изменятся. Измените дочерний элемент, и родительский изменится на «неопределенный».)

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

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

procedure PropagateCheckState(Tree: TVirtualStringTree; Node: PVirtualNode);
var
  Data: PNodeData;
  TargetID: string;
  Parent: PVirtualNode;
  FoundOne: Boolean;
begin
  Data := Tree.GetNodeData(Node);
  TargetID := Data.SkypeID;

  Parent := Tree.GetFirst;
  while Assigned(Parent) do begin
    // Assume no user appears twice in the same category
    if Parent <> Tree.NodeParent[Node] then begin
      FoundOne := False;
      Child := Tree.GetFirstChild(Parent);
      while Assigned(Child) and not FoundOne do begin
        Data := Tree.GetNodeData(Child);
        if Data.SkypeID = TargetID then begin
          // Found a duplicate. Sync it with Node.
          Tree.CheckState[Child] := Tree.CheckState[Node];
          FoundOne := True;
        end;
        Child := Tree.GetNextSibling(Child);
      end;
    end;
    Parent := Tree.GetNextSibling(Parent);
  end;
end;

procedure TSkypeListEventHandler.vtSkypeChecked(Sender: TBaseVirtualTree; Node: PVirtualNode);
var
  CheckedEvent: TVTChangeEvent;
begin
  if Sender.GetNodeLevel(Node) = 0 then
    exit; // The tree cascades changes automatically

  Assert(Sender.GetNodeLevel(Node) = 1, 'Unexpected node level');
  // We'll be accessing members that are protected in TBaseVirtualTree, but
  // they're public in TVirtualStringTree, so make sure we're still operating
  // on the same tree.
  Assert(Sender = vtSkype);

  CheckedEvent := vtSkype.OnChecked;
  vtSkype.OnChecked := nil;
  try
    PropagateCheckState(vtSkype, Node);
  finally
    vtSkype.OnChecked := CheckedEvent;
  end;
end;

Если бы в вашей структуре данных был список всех узлов, связанных с данным идентификатором пользователя, это было бы гораздо проще:

procedure PropagateCheckState(Tree: TVirtualStringTree; Node: PVirtualNode);
var
  Data: PNodeData;
  i: Integer;
begin
  Data := Tree.GetNodeData(Node);

  for i := 0 to Pred(Data.User.Nodes.Count) do
    Tree.CheckState[Data.User.Nodes[i]] := Tree.CheckState[Node];
end;

Даже если вы продолжите хранить все свои данные в деревеСам по себе контроль (который вам неоднократно советовали - плохая идея), вы все равно можете использовать вторичную структуру данных, чтобы действовать как index для узлов дерева, отключенных от идентификатора пользователя.Если у вас достаточно свежая версия Delphi, вы можете использовать TDictionary<string, TList<PVirtualNode>>.Тогда PropagateCheckState может выглядеть так:

uses Generics.Collections;

var
  UserNodes: TDictionary<string, TList<PVirtualNode>>;

procedure PropagateCheckState(Tree: TVirtualStringTree; Node: PVirtualNode);
var
  Data: PNodeData;
  Nodes: TList<PVirtualNode>;
  i: Integer;
begin
  Data := Tree.GetNodeData(Node);

  if not UserNodes.TryGetValue(Data.SkypeID, Nodes) then
    exit; // Weird. The node's ID isn't in the index at all.

  for i := 0 to Pred(Nodes.Count) do
    Tree.CheckState[Nodes[i]] := Tree.CheckState[Node];
end;

Обязательно обновляйте индекс UserNodes всякий раз, когда вы добавляете или удаляете пользователя из категории.

0 голосов
/ 04 июля 2013

Для обхода по всем узлам может быть полезна функция GetNext (PVirtualNode, bool childs) наподобие (извините за код C ++)

TVirtualNode* parent=NULL;
TVirtualNode* node=VST->GetFirst();
 while (node)
    {
    data=static_cast<TreeItemData*>(VST->GetNodeData(node));
    //doo something with node, data
    node=VST->GetNext(node, true);
    } 
0 голосов
/ 08 апреля 2011

Для простоты я предполагаю, что "Todd" содержится в классе, используемом TreeView для создания записей. Пока ваш TreeView запрашивает информацию из этого класса, вы можете уйти, добавив булеву проверку в самом классе и аннулировать представление дерева.
Когда дерево перерисовывается, оно будет использовать ваш класс для соответствующей установки флажков.

Я знаю, что VirtualTreeView достаточно быстр, чтобы выполнить это для нескольких тысяч записей за доли секунды.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...