мета-данные страницы
  •  

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Следующая версия
Предыдущая версия
unilinesworkwithdata [31.03.2022 07:31]
Lingri создано
unilinesworkwithdata [31.03.2022 08:11]
Строка 1: Строка 1:
-====== Работа с данными в UniLines ====== 
  
-В этой статье мы рассмотрим,​ как взаимодействовать с UniLines в рантайме,​ а именно:​ 
- 
-  * Как обращаться к хранящимся данным;​ 
-  * Как формировать таблицу без участия метаобъекта;​ 
-  * Как происходит добавление/​изменение/​удаление строк. 
- 
-===== Обращение к данным UniLines ===== 
- 
-[[https://​doc.agb.is/​unilinesdb|В прошлой статье]] уже было разобрано,​ что данные хранятся внутри каждой ноды, в поле NodeColList. Однако,​ обращаться к ним "​голым способом",​ прописывая каждый раз цикл поиска нужного значения,​ было бы очень... громоздко. 
- 
-В связи с этим широко используется следующее свойство:​ 
- 
-<sxh Delphi> 
-property Value[Name: string]:​variant read GetValue write SetValue; 
-</​sxh>​ 
- 
-Оно же используется не только для считывания,​ но и записи. 
- 
-Пример использования:​ 
- 
-<sxh Delphi> 
-var x, y: variant; 
-begin 
-  x := 3; 
-  ULF.Value['​x'​] := x; 
-  y := ULF.Value['​x'​];​ //<----y будет хранить значение 3 
-end 
-</​sxh> ​ 
- 
-Если крайне сжато, то это работает так: 
-  * В ULF должна существовать наша колонка '​x';​ 
-  * Для текущей выбранной строки в эту колонку '​x'​ будет записано значение 3; 
-  * В текущей выбранной строке из колонки '​x'​ взять значение. 
- 
-Теперь рассмотрим,​ как это происходит внутри. У Value есть геттер GetValue и сеттер SetValue. Внутри они используют функцию GetColIndex 
- 
-<sxh Delphi> 
-function TUniLinesFrame.GetValue(Name:​ string): variant; 
-var i: integer; 
-begin 
-  Result:​=null;​ 
-  if (curLine<>​nil) and (curLine.NodeColList<>​nil) then 
-  begin 
-    i:​=GetColIndex(Name);​ 
-    try 
-      Result:​=curLine.NodeColList.Items[i];​ 
-    except 
-    end; 
-  end; 
-end; 
- 
-procedure TUniLinesFrame.SetValue(Name:​ string; const Value: variant); 
-var i: integer; 
-begin 
-  if curLine<>​nil then 
-  begin 
-    i:​=GetColIndex(Name);​ 
-    curLine.NodeColList.Items[i]:​=Value;​ 
-    if (not (nsLoading in LinesState)) and (i<>​-1) and 
-      GetMetaColumn(i).Do_Summ 
-    then 
-      DoFullSumma(i);​ 
-  end; 
-end; 
- 
-function TUniLinesFrame.GetColIndex(fldName:​ string): int64; 
-var i: integer; 
-    s: string; 
-    cl: TMetaColumn;​ 
-begin 
-  Result:=-1; 
-  s:​=UpperCase(fldName);​ 
-  for i:=0 to Cls.Count-1 do 
-  begin 
-    cl := GetMetaColumn(i);​ 
-    if not cl.Is_LineNumber then 
-    begin 
-      if UpperCase(Fields[i])=s then 
-      begin 
-        Result:=i; 
-        Break; 
-      end; 
-    end 
-  end; 
-end; 
-</​sxh>​ 
- 
-Алгоритм действий таков: 
- 
-  - Вызвав x := ULF.Value['​x'​],​ мы вызываем геттер GetValue; 
-  - Внутри GetValue вызывается GetColIndex('​x'​);​ 
-  - GetColIndex прогоняет цикл по элементам списка Cls, и находит,​ есть ли колонка с отключенной опцией Is_LineNumber,​ и при этом имеется соответствующий элемент в списке названий колонок Fields и возвращает номер колонки; ​ 
-  - Затем берётся нужное значение из i-го элемента списка NodeColList. 
- 
-Похожим образом значение будет записано в NodeColList в сеттере SetValue, и вызваны дополнительные обработчики по суммированию данных. 
- 
-Ещё здесь стоит обратить внимание,​ что значения берутся из конкретного CurLine. CurLine - это указатель на данные конкретной ноды. Как он меняется,​ рассмотрим ниже. 
- 
-===== Как формировать таблицу без участия метаобъекта ===== 
- 
-UniLines можно формировать и без метаобъектов. На практике такое используется,​ к примеру,​ если нужно сделать многоуровневую структуру данных. В таком режиме работа с ULF становится ближе к работе напрямую с VST, но существуют некоторые упрощения,​ учитывающие уже известные нам возможности по хранению и доступа к данным. 
- 
-==== Добавление колонок ==== 
- 
-Во-первых,​ для такого ULF требуется вручную задать колонки,​ с которыми потом планируется работа. Для этих целей существует функция AddFieldColumn. 
- 
-Входящие параметры:​ 
-  * FieldName: string - внутреннее название колонки (только на английском языке, чтобы из кода можно было к ней обращаться);​ 
-  * ColName: string - название колонки для отображения в дереве (на любом языке);​ 
-  * Pas_type: string - название типа данных,​ который будет содержаться в колонке (одно из - STRING, INTEGER, DOUBLE, BOOLEAN, TDATETIME); 
-  * DisplayFormat:​ string - формат отображения значений вида DOUBLE; 
-  * MaxWidth: variant - максимальная ширина колонки в дереве. Передавать null, чтобы было без ограничений по размерам;​ 
-  * IsVisible : boolean = True - будет ли видима колонка в дереве или нет (например,​ можно скрыть колонки со значениями разных ID); 
-  * ADo_Summ: boolean = False - требуется ли суммирование по этой колонке внизу дерева (актуально для числовых значений);​ 
-  * AIs_Money: boolean = False - задаёт колонке зелёный фон; 
-  * DefaultWidth:​ integer = 0 - ширина колонки по умолчанию. В случае 0 расчёт ширины будет производить само VST. 
- 
-Кратко рассмотрим,​ что делает эта функция:​ 
- 
-<sxh Delphi> 
-function TUniLinesFrame.AddFieldColumn(FieldName,​ ColName, Pas_Type, 
-  DisplayFormat:​ string; MaxWidth: variant; IsVisible: boolean = True; 
-  ADo_Summ: boolean = False; AIs_Money: boolean = False; DefaultWidth:​ integer = 0): TMetaColumn;​ 
-var col: TVirtualTreeColumnFooter;​ 
-    cl: TMetaColumn;​ 
-    s, rd: string; 
-    Node: PVirtualNode;​ 
-    Dat: PLinesData; 
-    fr: TFrameColumn;​ 
-begin 
-  //​создаём колонку средством VST; 
-  col:​=AddColumn;​ 
-  {...} 
-  //​создаём соответствующую метаколонку;​ 
-  cl:​=TMetaColumn.Create(RdTr,​ TmpQuery.UpTr);​ 
-  Result:=cl; 
-  ​ 
-  {...указание различных параметров} 
-  //​создание объединённой колонки TFrame 
-  fr:​=ClsAdd(cl,​ col); 
-  Fields.AddObject(FieldName,​ fr); //​добавление названия в список названий колонок 
-  ValueFlds.AddObject(FieldName,​ fr); //​добавление названия колонок в список,​ который обрабатывается для удаления колонок 
-  ​ 
-  {...выставление оставшихся настроек} 
- 
-  // Добавляем в каждую ноду по пустому значению 
-  VST.BeginUpdate;​ 
-  try 
-    Node:​=VST.GetFirst;​ 
-    while Node<>​nil do 
-    begin 
-      Dat:​=VST.GetNodeData(Node);​ 
-      Dat^.NodeColList.Add(null);​ 
- 
-      Node:​=VST.GetNext(Node);​ 
-    end; 
-  finally 
-    VST.EndUpdate;​ 
-  end; 
-end; 
-</​sxh>​ 
- 
-Т.е. в конце функции,​ добавляющей колонку,​ для каждой существующей сейчас ноды в неё будет записан null. 
- 
-==== Добавление строк ==== 
- 
-После того, как были добавлены все необходимые колонки,​ можно добавлять строки. Это делается с помощью процедуры AddLine, основная логика которой такова:​ 
- 
-<sxh Delphi> 
-procedure TUniLinesFrame.AddLine(AddFirstLine:​ boolean = False); 
-var Node: PVirtualNode;​ 
-begin 
-  if AddFirstLine then 
-    Node:​=VST.InsertNode(nil,​ amAddChildFirst) 
-  else 
-    Node:​=VST.AddChild(nil);​ 
- 
-  {...выставление разных опций} 
- 
-  curLine:​=AddData(Node);​ 
- 
-  LastNode:​=Node;​ 
-end; 
-</​sxh>​ 
- 
-Напрямую в VST добавляется новая нода, либо в самое начало (если передали AddFirstLine = True), либо в конец. AddData заполняет соответствующий ноде NoceColList null-ами. 
- 
-Также обратим внимание на то, что AddLine изменяет значение curLine (с которым мы встретились при получении и присвоении значений колонки),​ и запоминает в целом новую ноду в LastNode. 
- 
-Смена указателя curLine нам позволяет писать код следующего вида: 
- 
-<sxh Delphi> 
-  ULFResults.AddLine;​ 
-  ULFResults.Value['​RESULT_ID'​] := objID; 
-  ULFResults.Value['​action_id'​] := q.FieldByName('​call_action_id'​).AsInt64;​ 
-  ULFResults.Value['​NAME'​] := q.FieldByName('​action_name'​).AsString;​ 
-  ULFResults.Value['​Type'​] := q.FieldByName('​type_name'​).AsString;​ 
-</​sxh>​ 
- 
-Т.е. добавив новую строку AddLine, мы сразу для новой строки можем указывать нужные значения. 
- 
-==== Перебор строк UniLines ==== 
- 
-Ещё одна типовая задача - пройтись по всем строкам UniLines. ​ 
- 
-Рассмотрим на конкретном примере. Модуль BnakStatemens.pas (журнал банковских выписок). В журнале есть кнопка,​ нажатие которой должно создать банковские документы по всем подходящим по условиям выпискам,​ которые отображаются в ULF. Реализация следующая (приведён немного упрощённый вид): 
- 
-<sxh Delphi> 
-    ULF.DisableControls;​ 
-    try 
-      ULF.FirstLine;​ 
-      for i:=0 to ULF.LinesCount-1 do 
-      begin 
-        if (ULF.Value['​CONTR_ID'​] <> NULL) and (ULF.Value['​contr_accoun_id'​] <> NULL) and (ULF.Value['​DOC_ID'​] = NULL) then 
-        begin 
-          try 
-            WriteLog('​Line'​);​ 
-            DocID := CreateBankDoc(ULF.Value['​CONTR_ID'​],​ ULF.Value['​contr_accoun_id'​],​ ULF.Value['​SUMMA'​],​ 0, 
-                                   ​ULF.Value['​BASES'​],​ ULF.Value['​DOC_NUM'​],​ ULF.Value['​DOC_DATE'​],​ ULF.Value['​is_incoming'​],​ ULF.Value['​ACCOUNT_IN'​]);​ 
-          except 
-            on E:Exception do 
-              SimpleMessage(E.Message);​ 
-          end; 
-        end; 
- 
-        ULF.NextLine;​ 
-      end; 
-    finally 
-      ULF.EnableControls;​ 
-    end; 
-</​sxh>​ 
- 
-Здесь мы видим целый набор новых функций - парные DisableControls,​ EnableControls;​ FirstLine; LinesCount; NextLine. Большинство из них интуитивно понятны,​ но разберём всё по порядку,​ что кого и зачем. 
- 
-Начнём с FirstLine: 
- 
-<sxh Delphi> 
-function TUniLinesFrame.FirstLine(const iFirstVisible:​ Boolean = True): Boolean; 
-var 
-  vNode: PVirtualNode;​ 
-  vDat: PLinesData; 
-begin 
-  {...предварительные проверки} 
-  if iIsVisible then 
-    vNode := VST.GetFirstVisible 
-  else 
-    vNode := VST.GetFirst; ​ 
- 
-  while vNode <> nil do 
-  begin 
-    vDat := VST.GetNodeData(vNode);​ 
-    if (vDat <> nil) and 
-       ​(vDat^.LineState <> lsEmpty) 
-    then 
-    begin 
-      VST.FocusedNode := vNode; 
-      VST.Selected[vNode] := True; 
-      Break; 
-    end; 
-    vNode := pNextNode(vNode,​ iFirstVisible);​ 
-  end; 
-end; 
-</​sxh>​ 
- 
-Ноды в VST могут быть видимые и невидимые,​ и в зависимости от переданного параметра iFirstVisible в качестве старта мы возьмём соответствующую ноду. От неё в VST ищется нода, имеющая данные,​ при этом у которой LineState <> lnEmpty. Затем, это важно, происходит фокусировка на этой ноде (VST.FocusedNode) и она считается выбранной (VST.Selected[vNode]). 
- 
-Мы уже знаем, что данные берутся из CurLine, но здесь он явно не меняется. Так каким образом мы получаем возможность получать данные из первой ноды? Это делается за счёт обработчика на VST - VSTFocusChanged:​ 
- 
-<sxh Delphi> 
-procedure TUniLinesFrame.VSTFocusChanged(Sender:​ TBaseVirtualTree;​ 
-  Node: PVirtualNode;​ Column: TColumnIndex);​ 
-begin 
-  curNode:​=Node;​ 
-  if Node<>​nil then 
-  begin 
-    curLine:​=VST.GetNodeData(Node);​ 
-    if Assigned(FOnLineClick) then 
-      if MetaObject<>​nil then 
-        FOnLineClick(MetaQuery.PrimaryColumn,​ MetaQuery.PrimaryField,​ curLine.NodeColList.Items[PrimaryIndex]) 
-      else 
-        FOnLineClick(nil,​ '',​ curLine.NodeColList.Items[PrimaryIndex]) 
-  end 
-  else 
-    curLine:​=nil;​ 
-end; 
-</​sxh>​ 
- 
-Он срабатывает в тот момент,​ когда мы делаем VST.FocusedNode := vNode; и именно за счёт этого обработчика мы меняем curLine и получаем возможность взять данные из первой ноды. Помимо этого срабатывает обработчик FOnLineClick,​ если он был задан (по умолчанию отсутствует). 
- 
-Теперь LinesCount - подсчёт числа строк. В зависимости от переданных параметров он может учитывать ноды с определённым LineState, учитывать или нет невидимые ноды, но всё это необязательные параметры,​ и в самой простой реализации он выглядит так: 
- 
-<sxh Delphi> 
-function TUniLinesFrame.LinesCount(LineState:​ TLineState = lsNone; OnlyVisible:​ boolean = True): int64; 
-var i: integer; 
-    Node: PVirtualNode;​ 
-    Dat: PLinesData; 
-begin 
-  i:=0; 
-  Node:​=VST.GetFirstVisible;​ 
-  while Node<>​nil do 
-  begin 
-    Dat:​=VST.GetNodeData(Node);​ 
-    if Dat^.LineState<>​lsEmpty then 
-      i:=i+1; 
- 
-    Node:​=VST.GetNextVisible(Node);​ 
-  end; 
-  Result := i; 
-end 
-</​sxh>​ 
- 
-Т.е. это простой счётчик числа видимых нод в VST, с полями самого ULF тут никакой работы не происходит. На очереди NextLine: 
- 
-<sxh Delphi> 
-function TUniLinesFrame.NextLine(const iNextVisible:​ Boolean = True): Boolean; 
-var 
-  vResNode, vCurNode: PVirtualNode;​ 
-  vDat: PLinesData; 
-begin 
-  {...предварительные инициализации} 
- 
-  //​поиск подходящей ноды ​ 
-  vCurNode := pNextNode(VST.FocusedNode,​ iNextVisible);​ 
-  while (vCurNode <> nil) do 
-  begin 
-    vDat := VST.GetNodeData(vCurNode);​ 
-    if (vDat <> nil) and 
-       ​(vDat^.LineState <> lsEmpty) 
-    then 
-    begin 
-      vResNode := vCurNode; 
-      Break; 
-    end; 
-    vCurNode := pNextNode(vCurNode,​ iNextVisible);​ 
-  end; 
-  ​ 
-  //​смена фокуса 
-  if (vResNode <> nil) and 
-     ​(VST.FocusedNode <> vResNode) 
-  then 
-  begin 
-    Result := True; 
-    VST.FocusedNode := vResNode; 
-    VST.Selected[vResNode] := True; 
-  end; 
-end; 
-</​sxh>​ 
- 
-В целом, NextLine похож на FirstLine. И он тоже меняет фокус, изменение которого меняет curLine. 
- 
-Смена фокуса чревата тем, что прогон цикла по ULF заставит каждую строку поочередно подсветиться синим цветом. Во-первых,​ это очень раздражает глаза, во-вторых,​ на это также тратится существенное время. Чтобы такого избежать были сделаны Disable/​EnableControls. Если сжато, то их принцип таков: 
-</sxh Delphi> 
-procedure TUniLinesFrame.DisableControls(DoSetLastAsDis:​ boolean = False); 
-var Node: PVirtualNode;​ 
-    Dat: PLinesData; 
-begin 
- 
-  VST.BeginUpdate;​ 
-  if curNode<>​nil then 
-    DisNode:​=curNode 
-  else 
-    DisNode:​=VST.FocusedNode;​ 
- 
-  {...дополнительный код, суть которого в том, что в DisNode, если он оказался пустым,​ записывается ссылка просто на первую видимую ноду} 
-end; 
- 
-procedure TUniLinesFrame.EnableControls;​ 
-begin 
-  VST.FocusedNode:​=nil;​ 
-  VST.ClearSelection;​ 
-  VST.EndUpdate;​ 
- 
-  if DisNode<>​nil then 
-  begin 
-    VST.FocusedNode:​=DisNode;​ 
-    ​ 
-    if VST.IsVisible[DisNode] then 
-      VST.Selected[DisNode]:​=True;​ 
-  end; 
-end; 
-</​sxh>​ 
- 
-Здесь очень важно понять,​ что DisableControls вызывает процедуру VST.BeginUpdate,​ что отключает любую перерисовку дерева. При этом мы также запоминаем ноду, на которой последний раз был фокус. EnableControls вызывает VST.EndUpdate,​ снова включая отрисовку,​ и возвращает фокус. Таким образом мы уходим от мельтешения строчек на экране и значительно ускоряется процесс прохода по строкам ULF. И т.к. мы выключили перерисовку VST, то весь цикл прогона обязательно надо выполнять в try-finally,​ и в finally выполнять EnableControls,​ иначе в случае какой-либо ошибки внутри цикла отрисовка будет потеряна. 
- 
-Таким образом типовой цикл по строкам ULF выглядит следующим образом:​ 
-<sxh Delphi> 
-    ULF.DisableControls;​ 
-    try 
-      ULF.FirstLine;​ 
-      for i:=0 to ULF.LinesCount-1 do 
-      begin 
-        //​бизнес-логика,​ что нам нужно сделать с нашей строкой. 
-        ULF.NextLine;​ 
-      end; 
-    finally 
-      ULF.EnableControls;​ 
-    end; 
-</​sxh>​ 
- 
- 
-[[https://​forms.gle/​F8XqnX6MdGa7ynqZ7|Пройти тест]] 
- 
-[[https://​doc.agb.is/​develop/​unilines|Назад]]