如何在Delphi中显示格式化(颜色,样式等)日志?

时间:2009-06-13 08:09:59

标签: delphi logging delphi-2009 rtf

我需要在Delphi 2009中显示格式化的日志。格式化不必实现说html的所有功能,而是一个小的子集,例如颜色,字体样式等。

目前我正在使用TRichEdit和我自己的专有标签,例如这是蓝色的。由于无法直接访问RTF文本,因此将其与TRichEdit配合使用非常复杂。例如,要将文本着色为蓝色,我必须:

  1. 解析附加文本,提取标签,确定需要格式化的文本以及如何格式化。
  2. 选择文字。
  3. 应用格式。
  4. 取消选择文本并将选择移动到文本的末尾,为下一个附加内容做好准备。
  5. 所有这些都是黑客和缓慢的。你知道用TRichEdit或其他更适合这项工作的控件来做更好(更快)的方法吗?

    我应该提一下,我考虑过在TWebBrowser中使用HTML。这种方法的问题是日志可以是1到100000行的任何地方。如果我使用普通的html查看器,我需要每次都设置整个文本而不是简单地附加它。

    此外,当我向其添加行时,需要实时更新日志。不只是从文件中读取并显示一次。

6 个答案:

答案 0 :(得分:9)

简单解决方案:使用带有自定义绘制方法的TListBox,并使用仅包含基本信息的对象将日志条目放在TObjectList中,而不是格式化(这将在演示代码中应用)。

或使用虚拟字符串列表/ VirtualTreeView组件。只会渲染需要显示的项目,这样可以节省资源。

答案 1 :(得分:4)

假设您的日志长度为1,000,000行,您可以忘记使用HTML或RTF,最干净的解决方案(我处理100-1,000,000)将使用(如mjustin建议的)TListBox

Style := lbVirtualOwnerDraw;
OnDrawItem := ListDrawItem; // your own function (example in help file)
  1. 以对应用程序其余部分有用的任何格式定义数据数组。我使用简单的LogObject。
  2. 每次对列表进行更改(添加,删除)时,将所有LogObject存储在ObjectList中,调整TListBox.Count以匹配新的ObjectList计数。
  3. 自己定义ListDrawItem以获取索引,您可以从您的ObjectList(数据库,等等......)获取信息并按需解析。
  4. 因为您一次只能查看几个条目,所以“按需解析”方法明显更好,因为在您尝试解析所有百万行时,加载时没有“减速”。

    不知道你的实际问题我可以说,根据我的经验,这是一种技术,一旦学习和掌握,在大多数面向数据的应用程序中都很有用。

    增强功能包括在列表框上方附加标题控件(我将它们包装在一个面板中),您可以创建一个优秀的TListView控件。将一些排序逻辑附加到标题控件上的click事件,您可以对对象列表进行排序,所有您需要做的就是调用ListBox.Invalidate来刷新视图(如果可以的话)。

    ++用于实时更新。我现在这样做,就是触发一个计时器事件来调整ListBox.Count,因为你不想每秒更新列表框1000次......: - )

答案 2 :(得分:1)

您可能想为Delphi购买词法扫描程序或源代码/语法荧光笔组件。有许多可用,大多数都不是很昂贵。在您的情况下,您需要测试一些并找到一个足以满足您需求的效率。

一些例子是:

为了突出显示非常大的日志文件,请查看专门突出显示文本文件的文件。他们应该非常快。但是RichEdit也没有懈怠。

答案 3 :(得分:1)

如果您决定按照建议使用TListbox,请确保您允许用户将他们正在查看的行的详细信息复制到剪贴板。没有什么比不能从日志中复制行更糟糕了。

答案 4 :(得分:0)

我收集你想要显示现有的纯文本日志,但是应用颜色吗?

以下是我能想到的几个选项:

  • 直接编写RTF; AFAIK,TRichEdit确实可以直接访问RTF代码;只需将PlainText属性切换为False,然后设置Text string属性。但是......祝你好运组装正确的RTF代码。
  • 将您的日志转换为HTML,并使用TWebBrowser控件显示它。
  • 使用Scintilla(或其他)突出显示控件,并滚动自己的语法高亮显示...

如果您自己编写日志,您也可以使用TRichEdit首先在RTF中生成日志。或者您可以使用HTML或XML生成日志(然后可以使用XSLT将其转换为您喜欢的任何内容)。

答案 5 :(得分:0)

对于那些感兴趣的人,这是我最终使用的代码。如果将此附加到TVirtualStringTree的OnAfterCellPaint事件,则会提供所需的结果。

(*
  DrawHTML - Draws text on a canvas using tags based on a simple subset of HTML/CSS

  <B> - Bold e.g. <B>This is bold</B>
  <I> - Italic e.g. <I>This is italic</I>
  <U> - Underline e.g. <U>This is underlined</U>
  <font-color=x> Font colour e.g.
                <font-color=clRed>Delphi red</font-color>
                <font-color=#FFFFFF>Web white</font-color>
                <font-color=$000000>Hex black</font-color>
  <font-size=x> Font size e.g. <font-size=30>This is some big text</font-size>
  <font-family> Font family e.g. <font-family=Arial>This is arial</font-family>
*)
procedure TfrmSNMPMIBBrowser.DrawHTML(const ARect: TRect; const ACanvas: TCanvas; const Text: String);

  function CloseTag(const ATag: String): String;
  begin
    Result := concat('/', ATag);
  end;

  function GetTagValue(const ATag: String): String;
  var
    p: Integer;
  begin
    p := pos('=', ATag);

    if p = 0 then
      Result := ''
    else
      Result := copy(ATag, p + 1, MaxInt);
  end;

  function ColorCodeToColor(const Value: String): TColor;
  var
    HexValue: String;
  begin
    Result := 0;

    if Value <> '' then
    begin
      if (length(Value) >= 2) and (copy(Uppercase(Value), 1, 2) = 'CL') then
      begin
        // Delphi colour
        Result := StringToColor(Value);
      end else
      if Value[1] = '#' then
      begin
        // Web colour
        HexValue := copy(Value, 2, 6);

        Result := RGB(StrToInt('$'+Copy(HexValue, 1, 2)),
                      StrToInt('$'+Copy(HexValue, 3, 2)),
                      StrToInt('$'+Copy(HexValue, 5, 2)));
      end
      else
        // Hex or decimal colour
        Result := StrToIntDef(Value, 0);
    end;
  end;

const
  TagBold = 'B';
  TagItalic = 'I';
  TagUnderline = 'U';
  TagBreak = 'BR';
  TagFontSize = 'FONT-SIZE';
  TagFontFamily = 'FONT-FAMILY';
  TagFontColour = 'FONT-COLOR';

var
  x, y, idx, CharWidth, MaxCharHeight: Integer;
  CurrChar: Char;
  Tag, TagValue: String;
  PreviousFontColor: TColor;
  PreviousFontFamily: String;
  PreviousFontSize: Integer;

begin
  // Start - required if used with TVirtualStringTree
  ACanvas.Font.Size := Canvas.Font.Size;
  ACanvas.Font.Name := Canvas.Font.Name;
  ACanvas.Font.Color := Canvas.Font.Color;
  ACanvas.Font.Style := Canvas.Font.Style;
  // End

  PreviousFontColor := ACanvas.Font.Color;
  PreviousFontFamily := ACanvas.Font.Name;
  PreviousFontSize := ACanvas.Font.Size;

  x := ARect.Left;
  y := ARect.Top;
  idx := 1;

  MaxCharHeight := ACanvas.TextHeight('Ag');

  While idx <= length(Text) do
  begin
    CurrChar := Text[idx];

    // Is this a tag?
    if CurrChar = '<' then
    begin
      Tag := '';

      inc(idx);

      // Find the end of then tag
      while (Text[idx] <> '>') and (idx <= length(Text)) do
      begin
        Tag := concat(Tag,  UpperCase(Text[idx]));

        inc(idx);
      end;

      ///////////////////////////////////////////////////
      // Simple tags
      ///////////////////////////////////////////////////
      if Tag = TagBold then
        ACanvas.Font.Style := ACanvas.Font.Style + [fsBold] else

      if Tag = TagItalic then
        ACanvas.Font.Style := ACanvas.Font.Style + [fsItalic] else

      if Tag = TagUnderline then
        ACanvas.Font.Style := ACanvas.Font.Style + [fsUnderline] else

      if Tag = TagBreak then
      begin
        x := ARect.Left;

        inc(y, MaxCharHeight);
      end else

      ///////////////////////////////////////////////////
      // Closing tags
      ///////////////////////////////////////////////////
      if Tag = CloseTag(TagBold) then
        ACanvas.Font.Style := ACanvas.Font.Style - [fsBold] else

      if Tag = CloseTag(TagItalic) then
        ACanvas.Font.Style := ACanvas.Font.Style - [fsItalic] else

      if Tag = CloseTag(TagUnderline) then
        ACanvas.Font.Style := ACanvas.Font.Style - [fsUnderline] else

      if Tag = CloseTag(TagFontSize) then
        ACanvas.Font.Size := PreviousFontSize else

      if Tag = CloseTag(TagFontFamily) then
        ACanvas.Font.Name := PreviousFontFamily else

      if Tag = CloseTag(TagFontColour) then
        ACanvas.Font.Color := PreviousFontColor else

      ///////////////////////////////////////////////////
      // Tags with values
      ///////////////////////////////////////////////////
      begin
        // Get the tag value (everything after '=')
        TagValue := GetTagValue(Tag);

        if TagValue <> '' then
        begin
          // Remove the value from the tag
          Tag := copy(Tag, 1, pos('=', Tag) - 1);

          if Tag = TagFontSize then
          begin
            PreviousFontSize := ACanvas.Font.Size;
            ACanvas.Font.Size := StrToIntDef(TagValue, ACanvas.Font.Size);
          end else

          if Tag = TagFontFamily then
          begin
            PreviousFontFamily := ACanvas.Font.Name;
            ACanvas.Font.Name := TagValue;
          end;

          if Tag = TagFontColour then
          begin
            PreviousFontColor := ACanvas.Font.Color;
            ACanvas.Font.Color := ColorCodeToColor(TagValue);
          end;
        end;
      end;
    end
    else
    // Draw the character if it's not a ctrl char
    if CurrChar >= #32 then
    begin
      CharWidth := ACanvas.TextWidth(CurrChar);

      if x + CharWidth > ARect.Right then
      begin
        x := ARect.Left;

        inc(y, MaxCharHeight);
      end;

      if y + MaxCharHeight < ARect.Bottom then
      begin
        ACanvas.Brush.Style := bsClear;

        ACanvas.TextOut(x, y, CurrChar);
      end;

      x := x + CharWidth;
    end;

    inc(idx);
  end;
end;