Announcement

Collapse
No announcement yet.

Can LISTVIEW columns have different fonts? Per column or cell?

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Can LISTVIEW columns have different fonts? Per column or cell?

    Can LISTVIEW columns have different fonts? Per column or cell?

    Anyone have a super good listview demo program?

  • #2
    Can have whatever - both column and/or cell. Simple example. Not 100% sure it is safe to select font into item's hDC like this, but can see no leak or problem doing it, so should be ok. And no, FONT NEW does not work with GDI functions - must use CreateFont API, like example below shows.
    Click image for larger version  Name:	ListViewFont.jpg Views:	0 Size:	36.5 KB ID:	785170
    Code:
    #COMPILE EXE
    #DIM ALL
    #INCLUDE "WIN32API.INC"
    #INCLUDE "COMMCTRL.INC"
    
    %IDC_LISTVIEW     = 400
    
    '====================================================================
    FUNCTION PBMAIN() AS LONG
      LOCAL hDlg, i AS LONG
       DIALOG NEW PIXELS, 0, "ListView Fonts Test",,, 280, 220, _
                          %WS_MINIMIZEBOX OR %WS_CAPTION OR %WS_SYSMENU, 0 TO hDlg
    
       CONTROL ADD LISTVIEW, hDlg, %IDC_LISTVIEW,"", 5, 5, 270, 210, _
                            %WS_CHILD OR %WS_TABSTOP OR %LVS_REPORT OR %LVS_SHOWSELALWAYS OR _
                            %LVS_SINGLESEL, %WS_EX_CLIENTEDGE
    
       LISTVIEW SET STYLEXX hDlg, %IDC_LISTVIEW, %LVS_EX_GRIDLINES OR %LVS_EX_FULLROWSELECT
       LISTVIEW INSERT COLUMN hDlg, %IDC_LISTVIEW, 1, "Default Font",    100, 0
       LISTVIEW INSERT COLUMN hDlg, %IDC_LISTVIEW, 2, "Times New Roman", 150, 0
    
       FOR i = 1 TO 20
          LISTVIEW INSERT ITEM hDlg, %IDC_LISTVIEW, i, 0, " Col 1  " + "Row" + STR$(i)
          LISTVIEW SET TEXT hDlg, %IDC_ListView, i, 2,    " Col 2  " + "Row " + STR$(i)
       NEXT i
    
       DIALOG SHOW MODAL hDlg CALL DlgProc
    END FUNCTION
    
    '====================================================================
    CALLBACK FUNCTION DlgProc() AS LONG
      LOCAL lplvcd AS NMLVCUSTOMDRAW PTR, nmCD AS NMCUSTOMDRAW PTR
      STATIC hFont AS DWORD
    
      SELECT CASE CB.MSG
      CASE %WM_INITDIALOG  ' < create a font and store the handle in a static (or global) variable
          hFont = MakeFontEx("Times New Roman", 10, %FW_BOLD, 1, 0)
    
      CASE %WM_DESTROY     ' < exit - delete what we have created
          DeleteObject hFont
    
      CASE %WM_NOTIFY
          SELECT CASE CB.NMID
          CASE %IDC_LISTVIEW
              SELECT CASE CB.NMCODE
              CASE %NM_CUSTOMDRAW
                 nmCD   = CB.LPARAM  ' < for item hDC
                 lplvcd = CB.LPARAM  ' < for iSubItem (column)
    
                 SELECT CASE @lplvcd.nmcd.dwDrawStage
                 CASE %CDDS_PREPAINT, %CDDS_ITEMPREPAINT
                     FUNCTION = %CDRF_NOTIFYITEMDRAW
    
                 CASE %CDDS_ITEMPREPAINT OR %CDDS_SUBITEM
                    IF  @lplvcd.iSubItem = 1 THEN       ' 0-based column count
                        @lplvcd.clrTextBk = %RGB_PALETURQUOISE ' < item BG color
                        @lplvcd.clrText = %RED          ' < just for fun..
                        SelectObject(@nmCD.hDC, hFont)  ' < is this safe?
                    END IF
                    FUNCTION = %CDRF_NEWFONT
                 END SELECT
              END SELECT
          END SELECT
      END SELECT
    END FUNCTION
    
    '====================================================================
    ' Create a desirable font and return its handle. Original code by Dave Navarro
    ' NOTE: enhanced with proper enumeration of character set via EnumCharSet
    '--------------------------------------------------------------------
    FUNCTION MakeFontEx(BYVAL FontName AS STRING, BYVAL PointSize AS LONG, BYVAL fBold AS LONG, _
                        BYVAL fItalic AS LONG, BYVAL fUnderline AS LONG) AS DWORD
      LOCAL hDC AS DWORD, CharSet AS LONG, CyPixels AS LONG
    
      hDC = GetDC(%HWND_DESKTOP)
        CyPixels  = GetDeviceCaps(hDC, %LOGPIXELSY)
        EnumFontFamilies hDC, BYVAL STRPTR(FontName), CODEPTR(EnumCharSet), BYVAL VARPTR(CharSet)
      ReleaseDC %HWND_DESKTOP, hDC
      PointSize = 0 - (PointSize * CyPixels) \ 72
    
      FUNCTION = CreateFont(PointSize, 0, _  'height, width(default=0)
                 0, 0, _                     'escapement(angle), orientation
                 fBold, _                    'weight (%FW_DONTCARE = 0, %FW_NORMAL = 400, %FW_BOLD = 700)
                 fItalic, _                  'Italic
                 fUnderline, _               'Underline
                 %FALSE, _                   'StrikeThru
                 CharSet, %OUT_TT_PRECIS, _
                 %CLIP_DEFAULT_PRECIS, %DEFAULT_QUALITY, _
                 %FF_DONTCARE , BYCOPY FontName)
    END FUNCTION
    '====================================================================
    ' Get type of character set - ansi, symbol.. a must for some fonts.
    '--------------------------------------------------------------------
    FUNCTION EnumCharSet (elf AS ENUMLOGFONT, ntm AS NEWTEXTMETRIC, _
                             BYVAL FontType AS LONG, CharSet AS LONG) AS LONG
      CharSet = elf.elfLogFont.lfCharSet
    END FUNCTION

    Comment


    • #3
      Thanks Borje

      I assume this is the basic idea if I wanted to have multi line cells too?

      I am trying to have a "display area" that has cells that look like this where I can pass in a top left lable in one font, a top right in another font, a variable length message body (multi line if nessary) and background color where the base control is a list box:

      Then have a function like ADD_ROW(mess1, mess2, mess2, bg_color)... What do you think?

      Click image for larger version  Name:	Snap63.jpg Views:	0 Size:	136.9 KB ID:	785178

      Easy to do in HTML - but I want the lightest control possible. And perhaps virtual list box.

      Comment


      • #4
        If purpose is "display area" only and data comes in as text, I suggest using a RichEdit control. Anyway, busy "baby sitting" our grand children this weekend, but can wrap up some example, maybe tomorrow evening. Need to know how data comes in.

        Comment


        • #5
          Thanks Borje!

          The "display area" needs to scroll and "area / record" is coming from a database. I had thought about using the web browser control and streaming in HTML. However I have always had a thing for super focused lightweight controls. I need to teach myself more about extending controls and custom controls.

          Comment


          • #6
            Ok, got some time off - here's one way to do it, using listbox with %LBS_OWNERDRAWVARIABLE style. Just noticed you wanted background color part too - forgot it, but not so hard to add if needed. Tried to include comments that explains the different parts. ListBox scroll latest added item into view.
            Click image for larger version

Name:	ListBoxFont.jpg
Views:	133
Size:	68.0 KB
ID:	785269
            Code:
            #COMPILE EXE
            #DIM ALL
            #INCLUDE "WIN32API.INC"
            %ID_LIST1 = 141
            
            '====================================================================
            FUNCTION PBMAIN
               LOCAL hDlg AS LONG
               DIALOG NEW 0, "ListBox font and item height test",,, 350, 180, _
                          %WS_CAPTION OR %WS_SYSMENU TO hDlg
            
               ' %LBS_OWNERDRAWVARIABLE style enables items with variable height
               CONTROL ADD LISTBOX, hDlg, %ID_LIST1, , 5, 5, 340, 170, _
                           %WS_CHILD OR %LBS_OWNERDRAWVARIABLE OR %LBS_HASSTRINGS OR _
                           %WS_TABSTOP OR %LBS_DISABLENOSCROLL OR %WS_VSCROLL, %WS_EX_CLIENTEDGE
            
               DIALOG SHOW MODAL hDlg, CALL DlgProc
            END FUNCTION
            
            '====================================================================
            CALLBACK FUNCTION DlgProc
              LOCAL c AS LONG, hFontOld, hPen AS DWORD, rc AS RECT, sz AS SIZEL
              LOCAL sFrom, sDate, sText AS STRING
              STATIC hBrush, hList, hFontSmall, hFontText AS DWORD
            
              SELECT CASE CB.MSG
              CASE %WM_INITDIALOG  ' create fonts and a brush
                   hFontSmall = MakeFontEx("Arial",  8, %FW_NORMAL, 0, 0, 0) ' header font
                   hFontText  = MakeFontEx("Arial", 10, %FW_NORMAL, 0, 0, 0) ' text font
                   hBrush     = CreateSolidBrush(RGB(236, 246, 255))         ' for every other listbox item background
                   hList      = GetDlgItem(CB.HNDL, %ID_LIST1)               ' need listbox handle
                   SendMessage(hList, %WM_SETFONT, hFontText, 0)             ' set listbox to use hFontText from start
            
                   RANDOMIZE        ' create some text items just for test purpose
                   FOR c = 1 TO 100 ' build 3-part string, to be parsed out later using CHR$(1) as delimiter
                       sText = "From: Bill Gates" + CHR$(1) + _
                               "Friday June 23, 2019 - 3:34 PM EST" + CHR$(1) + _
                                REPEAT$(RND(2, 20), "Blabla bla ")
                       Add_Row(hList, sText) ' SUB for adding text and setting item rect
                   NEXT
            
              CASE %WM_DESTROY  ' Delete what we have created at exit
                  DeleteObject hFontSmall
                  DeleteObject hFontText
                  DeleteObject hBrush
            
              CASE %WM_COMMAND
                  SELECT CASE CB.CTL
                  CASE %IDCANCEL  ' if to close dialog when Esc is triggered
                      IF CB.CTLMSG = %BN_CLICKED THEN DIALOG END CB.HNDL
            
                  CASE %ID_LIST1
                      SELECT CASE CB.CTLMSG
                      CASE %LBN_DBLCLK  ' show item text on double-click.. or whatever.
                          LISTBOX GET TEXT CB.HNDL, %ID_LIST1 TO sText
                          MSGBOX PARSE$(sText, CHR$(1), 1) + $CRLF + _
                                 PARSE$(sText, CHR$(1), 2) + $CRLF + $CRLF + _
                                 PARSE$(sText, CHR$(1), 3) + $CRLF
            
                      CASE %LBN_SELCHANGE
                          ' do whatever..
                      END SELECT
                  END SELECT
            
              CASE %WM_DRAWITEM  ' draw headers and text in each listbox item
                  LOCAL lpdis AS DRAWITEMSTRUCT PTR
                  IF CB.WPARAM = %ID_LIST1 THEN ' CB.WPARAM holds control's ID
                      lpdis = CB.LPARAM
                      IF @lpdis.itemID = &HFFFFFFFF THEN EXIT FUNCTION ' if no items
            
                      IF (@lpdis.itemState AND %ODS_SELECTED) = 0 THEN ' if not selected, use these colors
                          IF @lpdis.itemID MOD 2 = 0 THEN  ' serparate every other item using different colors
                              FillRect @lpdis.hDC, @lpdis.rcItem, GetSysColorBrush(%COLOR_WINDOW)
                              SetBkColor @lpdis.hDC, GetSysColor(%COLOR_WINDOW)
                          ELSE
                              FillRect @lpdis.hDC, @lpdis.rcItem, hBrush ' light blue background
                              SetBkColor @lpdis.hDC, RGB(236, 246, 255)  ' same as brush color
                          END IF
                          SetTextColor @lpdis.hDC, GetSysColor(%COLOR_WINDOWTEXT)
                      ELSE  ' if selected
                          FillRect @lpdis.hDC, @lpdis.rcItem, GetSysColorBrush(%COLOR_HIGHLIGHT)
                          SetBkColor @lpdis.hDC, GetSysColor(%COLOR_HIGHLIGHT)
                          SetTextColor @lpdis.hDC, GetSysColor(%COLOR_HIGHLIGHTTEXT)
                      END IF
            
                      rc        = @lpdis.rcItem  ' item rect
                      rc.nTop   = rc.nTop   + 4  ' top margin
                      rc.nLeft  = rc.nLeft  + 8  ' left margin
                      rc.nRight = rc.nRight - 8  ' right margin
            
                      c = SendMessage(hList, %LB_GETTEXTLEN, @lpdis.itemID, 0)   ' grab ListBox item's text
                      sText = SPACE$(c)  ' yes, we could use PB's ListBox Get Text too, but how fun is that? :)
                      SendMessage(hList, %LB_GETTEXT, @lpdis.itemID, STRPTR(sText))
            
                      sFrom    = PARSE$(sText, CHR$(1), 1)  ' split text into header and text parts
                      sDate    = PARSE$(sText, CHR$(1), 2)  ' Date part
                      sText    = PARSE$(sText, CHR$(1), 3)  ' Text part
                      hFontOld = SelectObject(@lpdis.hDC, hFontSmall)' select small font into DC - store original font
                      DrawText(@lpdis.hDC, BYVAL STRPTR(sFrom), LEN(sFrom), rc, %DT_LEFT)  ' Draw left side From/To part
                      DrawText(@lpdis.hDC, BYVAL STRPTR(sDate), LEN(sDate), rc, %DT_RIGHT) ' Draw right side date/time header part
            
                      GetTextExtentPoint32 @lpdis.hDC, BYVAL STRPTR(sText), LEN(sText), sz  ' need small font height
                      rc.nTop = rc.nTop + sz.cy + 4 ' set new top margin for text
            
                      SelectObject(@lpdis.hDC, hFontText) ' select text font into DC
                      DrawText(@lpdis.hDC, BYVAL STRPTR(sText), LEN(sText), rc, %DT_WORDBREAK)  ' draw actual text
                      SelectObject(@lpdis.hDC, hFontOld)  ' restore original font
            
                      ' Draw line between items
                      hPen = CreatePen(%PS_SOLID, 1, GetSysColor(%COLOR_3DFACE))  ' use pen with 3D face color (usually light gray)
                      hPen = SelectObject(@lpdis.hDC, hPen)
                      MoveToEx @lpdis.hDC, 0, @lpdis.rcItem.nBottom - 1, BYVAL %NULL
                      LineTo @lpdis.hDC, @lpdis.rcItem.nRight, @lpdis.rcItem.nBottom - 1
                      DeleteObject SelectObject(@lpdis.hDC, hPen)  ' always delete what we have created
                  END IF
              END SELECT
            
            END FUNCTION
            
            
            '==============================================================================
            ' Add text to ListBox, measure text rect and set item height to match text.
            ' Note, %DT_CALCRECT may return "rounded" rect height for some text lengths
            ' which in turn means some items can have bigger bottom margin.
            '------------------------------------------------------------------------------
            SUB Add_Row(BYVAL hList AS DWORD, BYVAL sText AS STRING)
              LOCAL c, hDC, hFont, TextHeight AS LONG, rc AS RECT
            
              GetClientRect(hList, rc) ' we need listbox width
              hDC = GetDc(hList)       ' get listbox Device Context
                hFont = SendMessage(hList, %WM_GETFONT, 0, 0) ' use the large font for measuring
                hFont = SelectObject(hDC, hFont) ' select big font into Device Context (DC)
                sText = sText + $CRLF            ' add a linefeed to ensure all text is shown
                c = SendMessage(hList, %LB_GETCOUNT, 0, 0) ' item 0 is item count 1
                SendMessage hList, %LB_ADDSTRING, 0, STRPTR(sText)
                TextHeight = DrawText(hDC, BYVAL STRPTR(sText), LEN(sText), _
                                      rc, %DT_CALCRECT OR %DT_WORDBREAK) ' get item height
                SendMessage hList, %LB_SETITEMHEIGHT, c, TextHeight + 10 ' add 10 for bottom margin
                hFont = SelectObject(hDC, hFont) ' restore font in DC
              ReleaseDC hList, hDC               ' release DC to avoid memory leak
            
              SendMessage hList, %WM_VSCROLL, MAKLNG(%SB_BOTTOM, 0), 0 ' scroll list to show latest added item
            
            END SUB
            
            
            '==============================================================================
            ' MakeFontEx - Create a font and return its handle. Original code by Dave Navarro
            ' NOTE: Here enhanced with proper enumeration of character set via EnumCharSet
            '------------------------------------------------------------------------------
            FUNCTION MakeFontEx(BYVAL sFont AS STRING, BYVAL PointSize AS LONG, _
                                BYVAL fBold AS LONG, BYVAL fItalic AS LONG, _
                                BYVAL fUnderline AS LONG, BYVAL StrikeThru AS LONG) AS DWORD
            
              LOCAL hDC AS DWORD, CharSet AS LONG, CyPixels AS LONG
            
              hDC = GetDC(%HWND_DESKTOP)
                CyPixels  = GetDeviceCaps(hDC, %LOGPIXELSY)
                EnumFontFamilies hDC, BYVAL STRPTR(sFont), CODEPTR(EnumCharSet), BYVAL VARPTR(CharSet)
              ReleaseDC %HWND_DESKTOP, hDC
              PointSize = 0 - (PointSize * CyPixels) \ 72
            
              FUNCTION = CreateFont(PointSize, 0, _  'height, width(default=0)
                         0, 0, _                     'escapement(angle), orientation
                         fBold, _                    'weight (%FW_DONTCARE = 0, %FW_NORMAL = 400, %FW_BOLD = 700)
                         fItalic, _                  'Italic
                         fUnderline, _               'Underline
                         StrikeThru, _               'StrikeThru
                         CharSet, %OUT_TT_PRECIS, _
                         %CLIP_DEFAULT_PRECIS, %DEFAULT_QUALITY, _
                         %FF_DONTCARE , BYCOPY sFont)
            
            END FUNCTION
            FUNCTION EnumCharSet (elf AS ENUMLOGFONT, ntm AS NEWTEXTMETRIC, _
                                  BYVAL FontType AS LONG, CharSet AS LONG) AS LONG
              CharSet = elf.elfLogFont.lfCharSet
            ' Get and return type of character set - ansi, symbol.. a must for some fonts!
            END FUNCTION

            Comment


            • #7
              WOW! Thanks Borje! I have to figure out what you did here! Super cool!

              Comment


              • #8
                David,
                You can even change the Header font.

                Comment


                • #9
                  CustomDraw in Windows allows some nice customizations using fonts and colors, like this:

                  Click image for larger version

Name:	pbcust01.png
Views:	74
Size:	7.3 KB
ID:	786130

                  Then there is OwnerDraw, which is more powerful. It allows you to literally draw each cell, like this:

                  Click image for larger version

Name:	pbcust02.png
Views:	54
Size:	9.5 KB
ID:	786131

                  The above were done using EZGUI so I don't have any SDK code to post here, but EZGUI does use only the normal WIN32 API methods to accomplish this.

                  Chris Boss
                  Computer Workshop
                  Developer of "EZGUI"
                  http://cwsof.com
                  http://twitter.com/EZGUIProGuy

                  Comment


                  • #10
                    Chris, Could you post or email me the code for the examples above. Love to see how you did this in EZGUI.

                    Comment

                    Working...
                    X