VST实例(3)绘制VST

发布时间 2023-05-29 15:08:43作者: Luo大哥

在绘制以及事件中,需要明确两个名词:

ITEM,通常指的是node,因为VST中一个节点就是一项。

CELL(单元格),通常指的是一个VST下的某具体栏(column)。

1、节点图标

VST可以链接两组图标,分别是vst.Images; vst.StateImages; vst.Images用于存储VST的通用图标,而vst.StateImages用于存储在不同状态下的图标。

事件OnGetImageIndex用于设置在不同情况下的图标,本程序的代码如下:

procedure TForm2.vstGetImageIndex(Sender: TBaseVirtualTree; Node: PVirtualNode;
  Kind: TVTImageKind; Column: TColumnIndex; var Ghosted: Boolean;
  var ImageIndex: TImageIndex);
begin
   if (Column=2) and (Kind=ikNormal) then
     ImageIndex:=Sender.GetNodeLevel(node)+1;
end;

上面的代码中,column=2,用于指定只在第三栏中显示图标,Kind=ikNormal说明正常情况下的显示图标。实际上,大部分时候每个节点都有个状态:normal,但是,处于编辑状态或其它特殊情况时,则不具备normal状态。

kind有以下状态:

    ikNormal

    ikSelected 

    ikState,

  ikOverlay

如果不指定状态,则会同时显示两个图标,一个是normal的图标,一个是状态图标。

上面指定了kind=iknormal,因此如果选中某行节点的时候,你会看到没有图标,所以语句可以更改为:

if (Column=2) and (Kind in [ikNormal,ikSelected ])

如果没有kind部分,则VST显示的效果如下:

 

请注意:OnGetImageIndex事件的程序不同版本有所区别,V7.X和V5.X并不兼容。

默认情况下,VST每个单元格(CELL,即某个node下的某个column)都可以有图标,所以需要指定具体需要显示图标的node和column的图标ID(imageindex)。

2、绘制循环

绘制VST的顺序如下:

  • 开始绘制之前 (OnBeforePaint)
  • 节点绘制之前 (OnBeforeItemPaint)
  • 节点擦除之前 (OnBeforeItemErase)
  • 节点擦除之后 (OnAfterItemErase)
  • 单元格绘制之前 (OnBeforeCellPaint)
  • 绘制文本(string trees only, OnPaintText)
  • 绘制结束 (OnAfterCellPaint)

1OnBeforePaint

此事件发生在绘制VST之前,一个VST指发生一次此事件。

这个阶段通常用于对绘画操作的目标画布(TargetCanvas)(例如窗口或打印机画布)进行进一步设置,比如更改映射模式或设置另一个裁剪区域。由于传递的画布并不直接用于进行实际绘画,因此设置其字体或颜色没有任何效果。基本上,仅影响将位图复制到目标画布的属性才有任何影响。

通常不使用此事件,除非打算定制VST的背景图(background)。本程序没有使用此事件。

2OnBeforeItemPaint

当准备绘制某个node之前,会触发此事件。

在这个阶段的事件中,您可以告诉树形结构是否要完全自己绘制节点,还是让树形结构绘制它。由于这是基于每个节点的,所以这是保持特殊布局而不必在绘画循环中进行所有操作的完美场所。注意:将事件中的CustomDraw参数设置为True将完全跳过节点的绘制,不会绘制像树形线、按钮、图片或擦除背景等标准内容的任何东西。因此,要显示节点的任何有用信息,请在OnBeforeItemPaint事件中进行。

这是第一个获取双缓冲画布用于绘制节点的阶段,因此如果您想设置特殊属性,这是一个很好的机会。请记住,特别是颜色是由树形结构根据特定规则设置的(焦点、选择等)。

注意:如果你设置了CustomDraw:=true,那么后面你将自己绘制所有的东西,VST不会帮你绘制文本、图标等。

此时也不会进行任何实际的绘制,因此本程序也无此事件的代码。

3onBeforeItemErase

英语的erase通常翻译为擦除,但和我们理解中的擦除不一样,它并不是减去某种属性,而是在选定的区域都附上某种属性,例如擦除的位置,都使用某种颜色,而区域的之外,则是其原本的颜色。

事件onBeforeItemErase发生在开始绘制节点(node)之前,这个阶段及其相关事件通常用于为节点设置不同的背景颜色或用与树形结构不同的特殊图案擦除背景。

代码如下:

procedure TForm2.vstBeforeItemErase(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; ItemRect: TRect;
  var ItemColor: TColor; var EraseAction: TItemEraseAction);
begin
  if Odd(NoDe.Index) then ItemColor:=clSilver;
end;

itemcolor指的是节点的背景色,如果没有在后面进行修改,则将在此处指定节点的背景色。

TItemEraseAction有三个值:

eaColor,   // 使用提供的颜色绘制背景。

eaDefault, // VST将绘制节点的背景(图形或颜色)

eaNone//将忽略颜色或图形的设置,使用上一个节点的颜色绘制背景。

需要注意的是,如果选择了eanone,在后面的程序中如果自己手动绘制了单元格,可能会出现文本的重影。

4OnAfterItemErase

此事件发生在完成节点背景绘制之后,每个节点只触发一次此事件,这个阶段及其相关事件用于在背景被擦除后进行额外的绘制。

本程序也无此事件的代码。

5onbeforecellpaint

此事件发生在绘制单元格之前,虽然在进入这个阶段之前(如果是第一次运行)已经进行了针对该节点的完整设置,但与onbeforeitemerase相比,此事件的不同之处是绘画被限制在当前列。现在还没有绘制线条或图片。

可在此事件中设置单元格的rect。本程序也没有此事件的代码。

6OnPaintText

此事件是VST特有的,而VDT没有此事件,用于文本绘制的设置,因为Virtual Treeview不知道如何绘制节点的内容,它将这个绘画委派给名为DoPaintNode的虚方法。派生类会覆盖此方法并执行适当的操作。例如,TVirtualDrawTree仅触发其OnDrawNode事件,而TVirtualStringTree准备目标画布,并允许应用程序通过触发OnPaintText重写一些或所有画布设置(字体等)。在此事件返回后,节点的文本/标题被绘制。当对齐和绘制文本时,改变的字体属性会被考虑在内。

本程序中此事件的代码如下:

procedure TForm2.vstPaintText(Sender: TBaseVirtualTree;
  const TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  TextType: TVSTTextType);
begin
  if Sender.Selected[node] then
  begin
    TargetCanvas.Font.Size:=14;
    TargetCanvas.Font.Style:=TargetCanvas.Font.Style+[fsUnderline,fsItalic];
  end;
end;

当某行节点被选择,则选择的文本有着不同的字体大小和样式。

此事件通常也不进行具体的绘制工作,具体的绘制工作在ondrawtext事件中写代码,也就是说,在此事件中通常只进行设置。事件ondrawtext的讲述将放到“3、绘制文本”中。

7OnAfterCellPaint

此事件发生于完成了单元格的绘制,此时已经完成单元格的绘制,包括选择框的绘制。

本程序也无此事件的代码。

3、绘制文本

文本的绘制在ondrawtext事件中实现,先上代码如下:

procedure TForm2.vstDrawText(Sender: TBaseVirtualTree; TargetCanvas: TCanvas;
  Node: PVirtualNode; Column: TColumnIndex; const Text: string;
  const CellRect: TRect; var DefaultDraw: Boolean);
var ss1,ss2:string; POS:Integer;  fc:TColor;fs:Integer;
begin
  case Column of
    0,1,3:
    begin
      //首先存储默认的字体大小和色号
      fc:=TargetCanvas.Font.Color;
      fs:=TargetCanvas.Font.Size;
      //SS1中存储特定文本,即lbledt1.Text,但转换为大写并清除前后的无用空格
      ss1:=lbledt1.Text;
      ss1:=ss1.Trim.ToUpper;
      //lbledt1为空或单元格中文本不包含lbledt1中的文本,则直接退出。
      if ss1.IsEmpty then Exit;
      if not Text.Contains(ss1) then Exit;
      //必须设置DefaultDraw为false,不然即便你自己对文本进行了绘制,
      //VST仍然会再次进行默认的绘制。
      DefaultDraw:=False;
      //根据不同的栏目和level进行不同的缩进
      if Column=0 then POS:=sender.GetNodeLevel(node)*10
      else
        POS:=0;
      //SS2暂时存储特定文本之前部分的文本,例如单元格文本是ZBAA,而SS1是BA,则SS2是Z
      SS2:=Text.Substring(0,TEXT.IndexOf(SS1));
      //如果SS2不为空
      if not ss2.IsEmpty then
      begin
        TargetCanvas.Font.Color:=fc;
        TargetCanvas.Font.size:=fs;
        TargetCanvas.TextOut(CellRect.left+POS,5,ss2);
        pos:=TargetCanvas.TextWidth(ss2)+POS+2;
      end;
      //字号12,颜色红色显示特定的文本
      TargetCanvas.Font.Color:=clRed;
      TargetCanvas.Font.size:=fs+2;
      TargetCanvas.TextOut(CellRect.left+POS,4,ss1);
      pos:=pos+targetcanvas.TextWidth(ss1)+2;
      //正常显示剩下的文本
      TargetCanvas.Font.Color:=fc;
      TargetCanvas.Font.size:=fs;
      ss2:=Text.Substring(ss2.Length+ss1.Length);
      TargetCanvas.TextOut(CellRect.left+POS,5,ss2);

    END;
    4:
    begin
      with pcodes(Sender.GetNodeData(NODE))^ do
      begin
        if rwy_style='单跑道' then
        begin
          DefaultDraw:=False;
          TargetCanvas.Font.Color:=clRed;
          TargetCanvas.Font.size:=12;
          TargetCanvas.TextOut(cellrect.Left+3,CellRect.Top+5,'');
          TargetCanvas.Font.Color:=clBlack;
          TargetCanvas.Font.size:=10;
          TargetCanvas.TextOut(cellrect.Left+18,CellRect.Top+5,'跑道');
        end;
      end;
    end;
    else ;
  end;
end;

这段代码对显示的文本进行了定制,总得来说,代码分成了两部分,第一部分针对的是第一栏、第二栏和第四栏,如果ICAO四字码、IATA三字码或机场/情报区名称中存在查询的字符,则查询的字符放大两号,且以红色显示,而其余部分则以10号字体。

第二部分以红色字体显示单跑道的“单”字,其余正常显示。

显示结果如下: