4

If you use ownerdraw with a TListView, the subitems are all BOLD font style by default somehow, even if the listview font.style is set to [], for all the SubItems following a custom drawn one.

A workaround I found is forcing the Style set in the CustomDrawSubItem event:

ListView2.Canvas.Font.Style := [fsItalic];
ListView2.Canvas.Font.Style := [];

(a simple call with [] won't work unless the default style is set to something other than [], because the SetStyle call doesn't think the style has changed)

This is however an ugly fix which involves extra processing time. Is there a better solution?

Demo project: http://www.mediafire.com/?v8bsdpvpfqy47vn

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
hikari
  • 3,393
  • 1
  • 33
  • 72
  • I'm just curious how much time is the _extra processing time_ it involves? While I agree with you this is ugly, I think the processing time is not a factor in this case. – jachguate Nov 21 '12 at 03:22
  • seems to be around 3 microseconds per subitem on an average laptop, so a LV with say 10 subitems and 30 items displayed, involves 0.3ms extra processing time whenever a redraw is needed. Obviously yeah that's nothing, but it's still an ugly fix, I'd like to know the root of this bug (I assume it is, as the JEDI version does not have this issue). – hikari Nov 21 '12 at 03:28
  • What is the value for ListView2.ParentFont? – jachguate Nov 21 '12 at 03:49
  • False, but it doesn't make a difference if set to default True. – hikari Nov 21 '12 at 03:51
  • This seems to happen mostly when you Draw on the canvas, every subitem after that = Bold. – hikari Nov 21 '12 at 04:05
  • It looks like the Canvas Font at the Delphi side is not in synch of the device context at the OS side, but I'm not the best on that area, which version of Delphi are you using? did you search for QC on the particular? – jachguate Nov 21 '12 at 04:08
  • Using XE3, but I've observed this issue for various previous versions. I'll upload a demo project in a sec. – hikari Nov 21 '12 at 04:13
  • Here http://www.mediafire.com/?v8bsdpvpfqy47vn – hikari Nov 21 '12 at 04:16
  • 1
    Look like a bug to me. If you _touch_ the canvas, it just mess up. All other drawing is a mess, for example hover with the mouse over any of the items. On the other hand, it (mal)functions the same in XE and XE2. I have no other Delphi at hand to check right now. – jachguate Nov 21 '12 at 05:14
  • 1
    That's another issue, I fix that with SetBkMode(TListView(Sender).Canvas.Handle, TRANSPARENT); ListView_SetTextBkColor(TListView(Sender).Handle, CLR_NONE); ListView_SetBKColor(TListView(Sender).Handle, CLR_NONE); – hikari Nov 21 '12 at 06:44
  • @David - It's not bold actually, it's the default 'system' font which looks a bit like bold. – Sertac Akyuz Nov 22 '12 at 01:26
  • 1
    @hikari, you're a genius ! Thanks for that SetBkMode(...TRANSPARENT)... That saves me ! – Marus Gradinaru Sep 12 '19 at 17:23
  • That's the same issue asked [here](https://stackoverflow.com/questions/8192961/is-there-a-bug-in-the-delphi-list-view-control-when-using-custom-drawing). – Sertac Akyuz Sep 12 '19 at 18:52

2 Answers2

4

I haven't encountered the exact situation you describe, but I have encountered a similar issue. When I use an owner-drawn TListView with an OnAdvancedCustomDrawSubItem event assigned to change the Canvas.Font on a per-subitem basis, I find that after I have changed the Sender.Canvas.Font for one subitem, subsequent subitems get drawn with the wrong settings even if I change the Sender.Canvas.Font for them. My workaround is to manually call the Sender.Canvas.Font.OnChange event handler at the end of my OnAdvancedCustomDrawSubItem event handler. That signals TListView to report back CDRF_NEWFONT to Windows, then everything gets drawn correctly. It is as if the Sender.Canvas.Font.OnChange event is not hooked up correctly while TListView is being owner-drawn, so it does not detect font changes and thus does not report back to Windows correctly.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • it seems faster than changing canvas brush etc also, 0 microsecs in my test app, while other methods involve 1-3 mcs. (testing with queryperformancecounter etc) – hikari Nov 22 '12 at 00:03
  • 2
    `Canvas.Font` does not use the `Sender` parameter of its `OnChange` event, so it can be anything you want, even `nil`. However, for good measure, I would suggest passing the `Canvas.Font` itself, in case that ever changes in the future: `Sender.Canvas.Font.OnChange(Sender.Canvas.Font); ` – Remy Lebeau Nov 22 '12 at 00:38
  • @hikari - Have you timed out not using the canvas, i.e. GetDC/ReleaseDC? I have not, but looking at the code path, I would be surprised if it took more time then resetting canvas font. As a bonus, you also can get rid of bitmap related code. :) – Sertac Akyuz Nov 22 '12 at 01:31
  • Timed both methods, both show 0 microseconds, but I'll switch to your code, looks cleaner than using bitmaps. (still needs the font fix however). – hikari Nov 22 '12 at 02:00
  • Actually had an issue with your code in my full app due to several drawing subitems, releasing the DC caused the images to dissapear. This works fine instead ImageList_Draw(TypeImages.Handle, 0, Sender.Canvas.Handle, R.Left - 2, R.Top, ILD_NORMAL); – hikari Nov 22 '12 at 04:26
4

I agree with jachguate's comment with that there seems to be an issue with the VCL control; a possibe design issue with TCustomListView.CNNotify. But it's not easy to follow the logic there.

One solution is to force a change on the canvas of the control when DefaultDraw is true, so that the VCL creates and selects the control's font again to the passed DC before the custom drawing notification returns. Example:

procedure TForm1.LVCustomDrawSubItem(Sender: TCustomListView; Item: TListItem;
  SubItem: Integer; State: TCustomDrawState; var DefaultDraw: Boolean);
Var R: TRect;
    bmp: TBitmap;
    x: Integer;
begin
  DefaultDraw := True;

  if SubItem = 1 then begin
    DefaultDraw := False;
    ...
      ...       
      Sender.Canvas.Draw(R.Left - 2, R.Top, Bmp);
      Bmp.Free;
    end;
  end;

  if DefaultDraw then
    Sender.Canvas.Brush.Color := ColorToRGB(clWindow);     // <--
end;


The way I would prefer is to avoid using the control's canvas, if at all possible. You can use a temporary DC for your case, this also avoids the black background problem mentioned in the comments to the question.

uses
  commctrl;

  ...

procedure TForm1.LVCustomDrawSubItem(Sender: TCustomListView; Item: TListItem;
  SubItem: Integer; State: TCustomDrawState; var DefaultDraw: Boolean);
Var R: TRect;
    bmp: TBitmap;
    x: Integer;
    DC: HDC;
begin
  DefaultDraw := True;

  if SubItem = 1 then begin
    DefaultDraw := False;
    ...
      ...

      DC := GetDC(Sender.Handle);
      ImageList_Draw(TypeImages.Handle, 0, DC, R.Left - 2, R.Top, ILD_NORMAL);
      ReleaseDC(Sender.Handle, DC);

      Bmp.Free;
    end;
  end;
end;
Community
  • 1
  • 1
Sertac Akyuz
  • 54,131
  • 4
  • 102
  • 169