Delphi TListview OwnerDraw SubItems - change default font (it's bold somehow after you Draw on the canvas)
Asked Answered
M

2

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

Mummify answered 21/11, 2012 at 3:11 Comment(13)
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.Laroche
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).Mummify
What is the value for ListView2.ParentFont?Laroche
False, but it doesn't make a difference if set to default True.Mummify
This seems to happen mostly when you Draw on the canvas, every subitem after that = Bold.Mummify
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?Laroche
Using XE3, but I've observed this issue for various previous versions. I'll upload a demo project in a sec.Mummify
Here mediafire.com/?v8bsdpvpfqy47vnMummify
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.Laroche
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);Mummify
@David - It's not bold actually, it's the default 'system' font which looks a bit like bold.Debacle
@hikari, you're a genius ! Thanks for that SetBkMode(...TRANSPARENT)... That saves me !Octofoil
That's the same issue asked here.Debacle
F
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.

Fantasize answered 21/11, 2012 at 18:13 Comment(5)
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)Mummify
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); Fantasize
@Mummify - 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. :)Debacle
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).Mummify
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);Mummify
D
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;
Debacle answered 21/11, 2012 at 18:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.