2009-06-13 11 views
5

Tôi cần hiển thị nhật ký được định dạng trong Delphi 2009. Định dạng không cần phải triển khai tất cả các tính năng của html nói, mà là một tập con nhỏ, ví dụ: màu sắc, kiểu chữ, v.v.Làm cách nào để hiển thị nhật ký được định dạng (màu, kiểu, v.v.) trong Delphi?

Hiện tại tôi đang sử dụng thẻ TRichEdit và các thẻ sở hữu của riêng mình, ví dụ: đây là màu xanh. Nó là khá phức tạp để có được điều này để làm việc với một TRichEdit vì không có quyền truy cập trực tiếp vào văn bản RTF. Ví dụ: để tô màu văn bản màu xanh, tôi phải:

  1. Phân tích văn bản được nối thêm giải nén thẻ, tìm ra văn bản cần được định dạng và cách thức.
  2. Chọn văn bản.
  3. Áp dụng định dạng.
  4. Bỏ chọn văn bản và di chuyển lựa chọn đến cuối văn bản đã sẵn sàng cho phần nối tiếp theo.

Tất cả điều này đều bị hack và chậm. Bạn có biết cách nào tốt hơn (nhanh hơn) để làm điều này với TRichEdit hoặc một điều khiển khác phù hợp hơn với công việc không?

Tôi nên đề cập đến rằng tôi đã xem xét việc sử dụng HTML trong TWebBrowser. Vấn đề với cách tiếp cận này là nhật ký có thể ở bất kỳ đâu từ 1 đến 100000 dòng. Nếu tôi sử dụng trình xem html bình thường, tôi cần đặt toàn bộ văn bản mỗi lần thay vì chỉ cần thêm văn bản.

Ngoài ra, nhật ký cần được cập nhật trong thời gian thực khi tôi thêm các dòng vào nó. Không chỉ đơn giản là đọc từ một tập tin và hiển thị một lần.

Trả lời

9

Giải pháp đơn giản: sử dụng TListBox với phương thức vẽ tùy chỉnh và đặt mục nhập nhật ký vào TObjectList bằng các đối tượng chỉ chứa thông tin cơ bản chứ không phải định dạng (điều này sẽ được áp dụng trong mã trình bày).

Hoặc sử dụng thành phần chuỗi ảo/VirtualTreeView. Chỉ các mục cần được hiển thị mới được hiển thị, điều này sẽ tiết kiệm tài nguyên.

+1

+1 cho Virtual TreeView – gabr

+0

Điều này có nhược điểm duy nhất là không thể chọn và sao chép văn bản vào khay nhớ tạm. – mghie

0

Tôi thu thập bạn muốn hiển thị bản ghi văn bản thuần tuý hiện có, nhưng áp dụng màu cho nó?

Dưới đây là một vài lựa chọn tôi có thể nghĩ:

  • Viết RTF trực tiếp; AFAIK, TRichEdit cung cấp truy cập trực tiếp vào mã RTF; chỉ cần chuyển thuộc tính PlainText thành False, sau đó thiết lập thuộc tính chuỗi văn bản. Nhưng ... may mắn lắp ráp đúng mã RTF.
  • Chuyển đổi nhật ký của bạn thành HTML và sử dụng điều khiển TWebBrowser để hiển thị nó.
  • Sử dụng Scintilla (hoặc khác) làm nổi bật điều khiển, và cuộn highlighter cú pháp riêng của bạn ...

Nếu bạn đang viết các đăng nhập, bạn cũng có thể sử dụng một TRichEdit để tạo ra các bản ghi trong RTF trong nơi đầu tiên. Hoặc bạn có thể tạo nhật ký bằng HTML hoặc trong XML (sau đó có thể được chuyển đổi thành bất kỳ thứ gì bạn thích, sử dụng XSLT).

+0

Không chính xác.Tôi muốn một bản ghi hiển thị và cuộn theo thời gian thực khi tôi thêm các dòng bổ sung vào nó. – norgepaul

4

Giả sử đăng nhập của bạn là 1.000.000 đường dài bạn có thể quên sử dụng HTML hoặc RTF, giải pháp sạch (và tôi xử lý 100-1,000,000) được sử dụng (như mjustin gợi ý) một TListBox với

Style := lbVirtualOwnerDraw; 
OnDrawItem := ListDrawItem; // your own function (example in help file) 
  1. Xác định mảng dữ liệu của bạn theo bất kỳ định dạng nào hữu ích cho phần còn lại của ứng dụng. Tôi đi với một LogObject đơn giản.
  2. Lưu trữ tất cả các LogObject trong một ObjectList, mỗi khi có một thay đổi cho danh sách (thêm, loại bỏ), điều chỉnh TListBox.Count để khớp với số lượng đối tượng mới.
  3. Xác định ListDrawItem mình để lấy chỉ mục và bạn có thể lấy thông tin từ youList ObjectList (cơ sở dữ liệu, bất kỳ ..) và phân tích cú pháp theo yêu cầu.

Vì bạn sẽ chỉ xem một vài mục cùng một lúc, phương pháp "phân tích cú pháp theo yêu cầu" tốt hơn đáng kể vì không có "chậm" tại thời điểm tải khi bạn cố gắng phân tích cú pháp tất cả triệu dòng.

Không biết vấn đề thực tế của bạn Tôi chỉ có thể nói rằng trong kinh nghiệm của tôi đây là một kỹ thuật mà một khi đã học và làm chủ là hữu ích trong hầu hết các ứng dụng theo định hướng dữ liệu.

Tính năng nâng cao bao gồm tùy chọn kiểm soát tiêu đề phía trên hộp danh sách (tôi kết hợp chúng lại với nhau trong bảng) và bạn có thể tạo Điều khiển TListView vượt trội. Đính kèm một chút logic sắp xếp cho sự kiện nhấp chuột vào điều khiển tiêu đề và bạn có thể sắp xếp danh sách đối tượng và tất cả những gì bạn phải làm là gọi ListBox.Invalidate để làm mới chế độ xem (khi có thể).

++ Để cập nhật thời gian thực. Tôi làm điều này vào lúc này, là kích hoạt một sự kiện hẹn giờ để điều chỉnh ListBox.Count vì bạn không muốn cập nhật hộp danh sách 1000 lần một giây .. :-)

+0

Tôi sẽ không sử dụng bất kỳ thứ gì với tstrings/tstringlist nếu bạn vượt qua mức 100000-300000. Các reallocations của danh sách con trỏ làm cho pho mát swiss của bộ nhớ của bạn. –

1

Bạn có thể muốn mua một máy quét lexical hoặc thành phần highlighter mã nguồn/cú pháp cho Delphi. Có rất nhiều sẵn có và hầu hết không phải là rất tốn kém. Trong trường hợp của bạn, bạn sẽ muốn thử nghiệm một vài và tìm một cái đủ hiệu quả cho nhu cầu của bạn.

Một vài ví dụ là:

Để có hiệu quả trong việc đánh dấu một tệp nhật ký rất lớn, hãy xem những tệp chuyên về các tệp văn bản làm nổi bật. Chúng phải cực kỳ nhanh. Nhưng RichEdit thực sự không hề bị ảnh hưởng.

+1

Điều khiển chỉnh sửa văn bản/trình xem có lợi thế hơn hộp danh sách hoặc điều khiển lưới mà chúng cho phép chọn và sao chép văn bản. Scintilla dĩ nhiên là mã nguồn mở, và có thêm lợi ích (trên TSynEdit), thông tin kiểu dáng được duy trì trên mỗi ký tự và có thể được đặt tự do trong mã người dùng - nó không chỉ là thành phần tô sáng cú pháp, nó có thể được sử dụng cũng như văn bản dạng tự do, nơi thông tin tạo kiểu được xác định theo cách khác. Nó phải là một phù hợp hơn cho văn bản đăng nhập. – mghie

1

nếu bạn quyết định sử dụng TListbox như được đề xuất, hãy đảm bảo bạn cho phép người dùng sao chép chi tiết dòng họ đang xem vào khay nhớ tạm. Không có gì tệ hơn là không thể sao chép các dòng từ nhật ký.

0

Đối với những người quan tâm, đây là mã mà tôi đã sử dụng. Nếu bạn đính kèm điều này vào sự kiện OnAfterCellPaint của TVirtualStringTree, nó sẽ cho kết quả mong muốn.

(* 
    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;