Colorize and set font for ListView and Combobox items [Windows only]

Share your advanced PureBasic knowledge/code with the community.
User avatar
Zapman
Enthusiast
Enthusiast
Posts: 205
Joined: Tue Jan 07, 2020 7:27 pm

Colorize and set font for ListView and Combobox items [Windows only]

Post by Zapman »

This library extends some PureBasic native functions or add functions for customizing the items appearance of ListViewGadget and ComboBoxGadget.

Image

Function Extensions:
  • SetGadgetItemColor() can now be used with ListViewGadget and ComboBoxGadget
  • SetGadgetItemImage() can now be used with ListViewGadget.
Additions (only for ListViewGadget and ComboBoxGadget):
  • SetGadgetItemFont() and GetGadgetItemFont()
  • GetGadgetItemImage()
  • CheckGadgetItem()
  • GetGadgetCheckedState()
To be sure to get the last version and discover other Zapman libraries, you can visit https://www.editions-humanis.com/downlo ... ads_EN.htm

Code: Select all

;
;**********************************************************************************
;
;                            SetGadgetItemEx.pbi library
;                                   Windows only
;                              Zapman - March 2025 - 2
;
;          This file should be saved under the name "SetGadgetItemEx.pbi".
;
;    This library extends some PureBasic native functions or add functions for
;     customizing the items appearance of ListViewGadget and ComboBoxGadget.
;
;  Extensions :
;     SetGadgetItemColor() can now be used with ListViewGadget and ComboBoxGadget.
;     SetGadgetItemImage() can now be used with ListViewGadget.
;
;  Additions (only for ListViewGadget and ComboBoxGadget):
;     SetGadgetItemFont()
;       GetGadgetItemFont()
;     GetGadgetItemImage()
;     CheckGadgetItem()
;       GetGadgetCheckedState()
;
;   This library is compatible with all other Zapman libraries, including
;   SetGadgetColorEx.pbi and ApplyColorThemes.pbi
; 
;***********************************************************************************
;
CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf
;
;**********************************************************************************
;
;-               1- STRUCTURES GLOBALS AND CONSTANTES DECLARATIONS
;
#Zapman_Ignore = -1
;
Global SGI_GadgetsLineHeight = 18 ; <-- Must be at least equal to 16 to show entire icons.
#SGI_MenuBulletSize = 4
;
EnumerationBinary SGI_ShowSelectionMethod
  #SGI_Bullet = 1
  #SGI_Borders
  #SGI_SystemColor
  #SGI_ShadeBackground
EndEnumeration
;
Global SGI_ShowSelectionMethod
If SGI_ShowSelectionMethod = 0
  ; By changing the value of SGI_ShowSelectionMethod,
  ; you change the manner of showing that an item is selected.
  ; Usualy, a simple background shading is enough to do that,
  ; but it can be inefficient in a dark mode environment or
  ; when each item has its own particular color (or both).
  ; So, you can choose between four different methods
  ; (see the SGI_ShowSelectionMethod enumeration).
  ; Try each of them to decide.
  ;
  ; Note that your can eventually combine different methods.
  ; SGI_ShowSelectionMethod = #SGI_SystemColor | #SGI_Bullet
  ;
  SGI_ShowSelectionMethod =  #SGI_SystemColor | #SGI_Borders
EndIf
;
Structure GadgetItemStruct
  GadgetNum.i
  ODTType.i
  ItemPos.i
  Font.i
  BackColor.i
  TextColor.i
  ImageNum.i
  ImageHandle.i
  ItemChecked.i
EndStructure
;
Global NewList ItemSpecs.GadgetItemStruct()
;
;
;
;*******************************************************************************
;
;-                             2- GENERAL FUNCTIONS
;                     (possibly reusable by other programs)
;
;*******************************************************************************
;
Procedure LoadGadgetsDefaultFont()
  #SPI_GETNONCLIENTMETRICS = $0029
  Protected ncm.NONCLIENTMETRICS
  ncm\cbSize = SizeOf(NONCLIENTMETRICS)
  ;
  If SystemParametersInfo_(#SPI_GETNONCLIENTMETRICS, ncm\cbSize, @ncm, 0)
    Protected FontName$ = PeekS(@ncm\lfCaptionFont\lfFaceName)
    Protected FontSize = ncm\lfCaptionFont\lfHeight / DesktopResolutionY()
    ProcedureReturn LoadFont(#PB_Any, FontName$, FontSize)
  EndIf
EndProcedure
;
Global DefaultGadgetsFont = LoadGadgetsDefaultFont()
;
CompilerIf Not(Defined(GetImageFromShell32, #PB_Procedure))
  Procedure GetImageFromShell32(IconNum, ImgWidth, ImgHeight)
    ;
    Protected TransparentImage = CreateImage(#PB_Any, ImgWidth, ImgHeight, 32, #PB_Image_Transparent)
    Protected hIcon = ExtractIcon_(0, "shell32.dll", IconNum)
    ;
    If IsImage(TransparentImage) And hIcon
      Protected Dest_hDC = StartDrawing(ImageOutput(TransparentImage))
      If Dest_hDC
        DrawingMode(#PB_2DDrawing_AlphaBlend)
        Box(0, 0, ImgWidth, ImgHeight, RGBA(0, 0, 0, 0))
        DrawIconEx_(Dest_hDC, 0, 0, hIcon, ImgWidth, ImgHeight, 0, #Null, #DI_NORMAL)
        StopDrawing()
        DeleteDC_(Dest_hDC)
      EndIf
    EndIf
    DestroyIcon_(hIcon)
    ;
    ProcedureReturn TransparentImage
  EndProcedure
CompilerEndIf
;
CompilerIf Not(Defined(ResizeImageToIconSize, #PB_Procedure))
  Procedure ResizeImageToIconSize(SourceImage)
    ;
    If SourceImage
      If IsImage(SourceImage)
        SourceImage = ImageID(SourceImage)
      EndIf
      ;
      Protected ResizedImage = CreateImage(#PB_Any, GetSystemMetrics_(#SM_CXSMICON), GetSystemMetrics_(#SM_CYSMICON), 32, #PB_Image_Transparent)
      If ResizedImage
        If StartDrawing(ImageOutput(ResizedImage))
          DrawingMode(#PB_2DDrawing_AlphaBlend)
          DrawImage(SourceImage, 0, 0, GetSystemMetrics_(#SM_CXSMICON), GetSystemMetrics_(#SM_CYSMICON))
          StopDrawing()
          ProcedureReturn ResizedImage
        EndIf
      EndIf
    EndIf
    ProcedureReturn #Null
  EndProcedure
CompilerEndIf
;
CompilerIf Not(Defined(CreateIconFromImage, #PB_Procedure))
  Procedure CreateIconFromImage(hBitmap)
    ;
    Protected iconInfo.ICONINFO, bitmap.BITMAP
    ;
    If IsImage(hBitmap)
      hBitmap = ImageID(hBitmap)
    EndIf
    ;
    If GetObject_(hBitmap, SizeOf(BITMAP), @bitmap)
      ;
      ; Fill the ICONINFO Structure
      iconInfo\fIcon = #True
      iconInfo\xHotspot = 0
      iconInfo\yHotspot = 0
      iconInfo\hbmMask = CreateBitmap_(bitmap\bmWidth, bitmap\bmHeight, 1, 1, #Null)
      iconInfo\hbmColor = hBitmap
      Protected hIcon = CreateIconIndirect_(@iconInfo)
      ;
      ; Free the mask
      DeleteObject_(iconInfo\hbmMask)
      ;
      ProcedureReturn hIcon
    EndIf
  EndProcedure
CompilerEndIf
;
CompilerIf Not(Defined(DrawTransparentRectangle, #PB_Procedure))
  Procedure DrawTransparentRectangle(Dest_hDC, *rect.RECT, CoverColor, Opacity)
    ;
    Protected TempRect.Rect, hBrush
    Protected Srce_hDC = CreateCompatibleDC_(Dest_hDC)
    ;
    If CoverColor = 0 : CoverColor = 1 : EndIf ; There is a bug when color = 0
    ;
    If Srce_hDC
      Protected ImgWidth = *rect\right - *rect\left
      Protected ImgHeight = *rect\bottom - *rect\top
      Protected hbmTemp = CreateCompatibleBitmap_(Dest_hDC, ImgWidth, ImgHeight)
      If hbmTemp
        Protected oldBitmap = SelectObject_(Srce_hDC, hbmTemp)
        If oldBitmap
          Protected blend, *blend.BLENDFUNCTION = @blend
          ;
          If OpenLibrary(0, "Msimg32.dll")
            ;
            ; Fill hbmTemp with CoverColor
            TempRect\left = 0 : TempRect\top = 0
            TempRect\right = ImgWidth : TempRect\bottom = ImgHeight
            hBrush = CreateSolidBrush_(CoverColor)
            FillRect_(Srce_hDC, TempRect, hBrush)
            DeleteObject_(hBrush)
            ;
            ; Copy hbmTemp from Srce_hDC to Dest_hDC, respecting AlphaBlend:
            *blend\BlendOp = #AC_SRC_OVER
            *blend\BlendFlags = 0
            *blend\AlphaFormat = 0
            *blend\SourceConstantAlpha = Opacity
            CallFunction(0, "AlphaBlend", Dest_hDC, *rect\left, *rect\top, ImgWidth, ImgHeight, Srce_hDC, 0, 0, ImgWidth, ImgHeight, blend)
            ;
            CloseLibrary(0)
          EndIf
          ;
          SelectObject_(Srce_hDC, oldBitmap)
        EndIf
        DeleteObject_(hbmTemp)
      EndIf
      DeleteDC_(Srce_hDC)
    EndIf
  EndProcedure
CompilerEndIf
;
CompilerIf Not(Defined(DrawRightPointingTriangle, #PB_Procedure))
  Procedure DrawRightPointingTriangle(hDC, *rc.Rect, tSize, FrontColor, BackColor)
    ;
    Protected *points = AllocateMemory(3 * SizeOf(POINT))
    Protected vCenter = *rc\top + (*rc\bottom - *rc\top) / 2 - 1
    ;
    ; Calculate the coordinates of the triangle
    PokeL(*points + 0, *rc\left + tSize) ; Point for the tip of the triangle
    PokeL(*points + 4, vCenter)  ; Centered vertically within the triangle
    ;
    PokeL(*points + 8, *rc\left)
    PokeL(*points + 12, vCenter + tSize) ; Bottom of the triangle
    ;
    PokeL(*points + 16, *rc\left);
    PokeL(*points + 20, vCenter - tSize) ; Top of the triangle
    ;
    Protected hPen = CreatePen_(#PS_SOLID, 1, FrontColor) ; Pen with 1-pixel thickness
    Protected hBrush = CreateSolidBrush_(BackColor)
    SelectObject_(hDC, hPen)
    SelectObject_(hDC, hBrush)
    ;
    Polygon_(hDC, *points, 3) ; Draw the triangle
    ;
    ; CleanUp
    DeleteObject_(hPen)
    DeleteObject_(hBrush)
    FreeMemory(*points)
  EndProcedure
CompilerEndIf
;
CompilerIf Not(Defined(DrawCheckmark, #PB_Procedure))
  Procedure DrawCheckmark(hDC, *rc.Rect, Size, Color)
    Protected pt.POINT, hPen
    
    ; Calcul de la position du rectangle 10x10 à l'intérieur de rc
    pt\x = *rc\left  ; Aligné à gauche
    pt\y = *rc\top + (*rc\bottom - *rc\top - Size) / 2; Centrage vertical
    
    ; Création d'un stylo pour dessiner la checkmark
    hPen = CreatePen_(#PS_SOLID, 2, Color)
    SelectObject_(hDC, hPen)
    
    ; Dessiner la checkmark (?) avec MoveToEx_ et LineTo_
    MoveToEx_(hDC, pt\x + Size/5, pt\y + Size * 2 / 5, 0)
    LineTo_(hDC, pt\x + Size * 3 / 5, pt\y + Size * 4 / 5)
    LineTo_(hDC, pt\x + Size * 7 / 5, pt\y)
    
    ; Nettoyage
    DeleteObject_(hPen)
  EndProcedure
CompilerEndIf
;
CompilerIf Not(Defined(RefreshGadget, #PB_Procedure))
  Procedure RefreshGadget(GadgetNum)
    If IsWindowVisible_(GadgetID(GadgetNum))
      HideGadget(GadgetNum, #True)
      HideGadget(GadgetNum, #False)
    EndIf
  EndProcedure
CompilerEndIf
;
; *****************************************************************************
;
;-                 3. SPECIALIZED PROCEDURES OF THE LIBRARY
;
;      The following procedure are subroutines of the library functions.
;
; *****************************************************************************
;
Procedure SGI_GetODTTypeFromPBGadgetType(PBGadgetType)
  Protected ODTType
  Select PBGadgetType
    Case #PB_GadgetType_ComboBox
      ODTType = #ODT_COMBOBOX
    Case #PB_GadgetType_ListView
      ODTType = #ODT_LISTBOX
  EndSelect
  ProcedureReturn ODTType
EndProcedure
;
Procedure SGI_RetreiveItemSpecs(GadgetNum, ItemNum)
  ForEach ItemSpecs()
    If ItemSpecs()\GadgetNum = GadgetNum And ItemSpecs()\ItemPos = ItemNum
      SetGadgetItemData(GadgetNum, ItemNum, ItemNum)
      ProcedureReturn #True
      Break
    EndIf
  Next
EndProcedure
;
Procedure SGI_OwnerDrawnCallback(hWnd, uMsg, wParam, lParam)
  ;
  ; Callback procedure for the main window hosting the gadgets
  ; whose items must be colored or set with a particular font.
  ;
  Protected *drawItem.DRAWITEMSTRUCT
  Protected *measureItem.MEASUREITEMSTRUCT
  Protected hDC, rc.RECT, rc2.RECT, Text$, ImgAddress
  Protected BackColor, TextColor, ItemChecked, hBrush, ImgVerticalMargin
  Protected GadgetNum, GadgetOrMenuHandle, ItemNum, ODTType
  Protected ItemFont, BackLuminosity, TextLuminosity
  Protected Selected, Disabled, Opacity, CoverColor
  ;
  Select uMsg
      ;
    Case #WM_DESTROY
      ; Clean the memory when the main window is destroyed:
      ;
      ForEach ItemSpecs()
        If IsImage(ItemSpecs()\ImageNum)
          FreeImage(ItemSpecs()\ImageNum)
          DeleteElement(ItemSpecs())
        EndIf
      Next
      ;
    Case #WM_MEASUREITEM
      *measureItem = lParam
      ;
      ODTType = *measureItem\CtlType
      If ODTType = #ODT_COMBOBOX
        *measureItem\itemHeight = SGI_GadgetsLineHeight
        ProcedureReturn #True
      EndIf
      ;
    Case #WM_DRAWITEM
      ;- Callback: #WM_DRAWITEM
      If lParam
        *drawItem = lParam
        ItemNum = *drawItem\itemID
        ODTType = *drawItem\CtlType
        hDC = *drawItem\hdc
        rc = *drawItem\rcItem
        GadgetNum = GetProp_(*drawItem\hwndItem, "PB_ID")
        If IsGadget(GadgetNum)
          If SGI_RetreiveItemSpecs(GadgetNum, ItemNum)
            ItemFont   = ItemSpecs()\Font
            BackColor  = ItemSpecs()\BackColor
            TextColor  = ItemSpecs()\TextColor
            ItemChecked  = ItemSpecs()\ItemChecked
            ImgAddress = ItemSpecs()\ImageHandle
          Else
            ItemFont   = #PB_Default
            BackColor  = #PB_Default
            TextColor  = #PB_Default
            ItemChecked  = 0
            ImgAddress = 0
          EndIf
          ;
          If IsFont(ItemFont) = 0
            ItemFont = DefaultGadgetsFont
          EndIf
          ;
          SelectObject_(hDC, FontID(ItemFont))
          GadgetOrMenuHandle = GadgetID(GadgetNum)
          Text$ = GetGadgetItemText(GadgetNum, ItemNum)
          ;
          ; _________________________________________
          ;
          ;              Layout settings
          ;
          ImgVerticalMargin = (SGI_GadgetsLineHeight - GetSystemMetrics_(#SM_CYSMICON)) / 2
          If *drawItem\ItemState & #ODS_SELECTED
            Selected = 1
          EndIf
          If *drawItem\ItemState & #ODS_GRAYED
            Disabled = 1
            Selected = 0
          EndIf
          ;
          ; _________________________________________
          ;
          ; Define colors for text and background:
          ;
          If BackColor = #PB_Default
            CompilerIf Defined(SetGadgetsColorsFromTheme, #PB_Procedure)
              ; Manage compatibility with the ApplyColorThemes.pbi library
              If ListSize(InterfaceColorPresets()) > 0
                BackColor = GetRealColorFromType("BackgroundColor", InterfaceColorPresets()\BackgroundColor)
              EndIf
            CompilerElseIf Defined(ObjectTheme, #PB_Module)
              ; Manage compatibility with the ObjectTheme.pbi library:
              BackColor = ObjectTheme::GetObjectThemeAttribute(0, #PB_Gadget_BackColor)
            CompilerEndIf
            If BackColor = #PB_Default
              BackColor = GetSysColor_(#COLOR_WINDOW)
            EndIf
          EndIf
          ;
          If TextColor = #PB_Default
            CompilerIf Defined(SetGadgetsColorsFromTheme, #PB_Procedure)
              ; Manage compatibility with the ApplyColorThemes.pbi library:
              If ListSize(InterfaceColorPresets()) > 0
                TextColor = GetRealColorFromType("TextColor", InterfaceColorPresets()\TextColor)
              EndIf
            CompilerElseIf Defined(ObjectTheme, #PB_Module)
              ; Manage compatibility with the ObjectTheme.pbi library:
              TextColor = ObjectTheme::GetObjectThemeAttribute(#PB_GadgetType_Button, #PB_Gadget_FrontColor)
            CompilerEndIf
            If TextColor = #PB_Default
              TextColor = GetSysColor_(#COLOR_WINDOWTEXT)
            EndIf
          EndIf
          ;
          ; Based on Human perception of color
          TextLuminosity = Red(TextColor)*0.299 + Green(TextColor)*0.587 + Blue(TextColor)*0.114
          BackLuminosity = Red(BackColor)*0.299 + Green(BackColor)*0.587 + Blue(BackColor)*0.114
          ;
          ; _________________________________________
          ;
          ; Paint the background:
          ;
          hBrush = CreateSolidBrush_(BackColor)
          FillRect_(hDC, @rc, hBrush)
          DeleteObject_(hBrush)
          ;
          ; _________________________________________
          ;
          ; Shade the background (eventually):
          ;
          If Selected And (SGI_ShowSelectionMethod & #SGI_ShadeBackground)
            ;
            If BackLuminosity > 128
              CoverColor = #Black
              Opacity = 40
            Else
              CoverColor = #White
              Opacity = 60
            EndIf
            ;
            DrawTransparentRectangle(hDC, rc, CoverColor, Opacity)
            ;
            If BackLuminosity < 128
              TextColor = #White
            Else
              TextColor = #Black
            EndIf
          EndIf
          ;
          ; _________________________________________
          ;
          ; Draw borders (eventually):
          ;
          If Selected And (SGI_ShowSelectionMethod & #SGI_Borders)
            hBrush   = CreateSolidBrush_(GetSysColor_(#COLOR_HIGHLIGHT))
            FrameRect_(hDC, @rc, hBrush)
            DeleteObject_(hBrush)
          EndIf
          ;
          ; _________________________________________
          ;
          ; Apply 'HotState' when SGI_ShowSelectionMethod & #SGI_SystemColor
          ;
          If Selected And (SGI_ShowSelectionMethod & #SGI_SystemColor)
            Opacity = 100
            CoverColor = GetSysColor_(#COLOR_HIGHLIGHT)
            If BackLuminosity < 128
              Opacity = 150
            EndIf
            ;
            DrawTransparentRectangle(hDC, rc, CoverColor, Opacity)
          EndIf
          ;
          ; _________________________________________
          ;
          ; Draw a checkmark (eventually):
          ;
          If ItemChecked
            CopyMemory(@rc, @rc2, SizeOf(RECT))
            rc2\right = rc2\left + DesktopScaledX(16)
           ; DrawThemeBackground_(hTheme, hDC, #MENU_POPUPCHECK, #MC_CHECKMARKNORMAL, @rc2, 0)
            ;DrawFrameControl_(hDC, @rc2, #DFC_MENU, #DFCS_MENUCHECK)
            DrawCheckmark(hDC, @rc2, DesktopScaledX(10), TextColor)
            rc\left + DesktopScaledX(18)
          EndIf
          ;
          ;
          ; _________________________________________
          ;
          ; Draw a bullet (eventually):
          ;
          If SGI_ShowSelectionMethod & #SGI_Bullet
            If Selected
              rc\left + DesktopScaledX(3)
              DrawRightPointingTriangle(hDC, rc, DesktopScaledX( #SGI_MenuBulletSize), TextColor, BackColor)
              rc\left + DesktopScaledX(#SGI_MenuBulletSize + 2)
            EndIf
          EndIf
          ;
          ; _________________________________________
          ;
          ; Draw an icon (eventually):
          ;
          If ImgAddress ; Image illustrating the menu line
            ;
            Protected icone = CreateIconFromImage(ImgAddress)
            If icone <> 0
              DrawIconEx_(hDC, rc\left, rc\top + ImgVerticalMargin, icone, 0, 0, 0, #Null, #DI_NORMAL)
              rc\left + GetSystemMetrics_(#SM_CXSMICON) + DesktopScaledX(3)
            EndIf
            ;
          EndIf
          ;
          ; _________________________________________
          ;
          ; Draw the item text:
          ;
          rc\Left + DesktopScaledX(2)
          ;
          SetTextColor_(hDC, TextColor)
          SetBkMode_(hDC, #TRANSPARENT)
          DrawText_(hDC, Text$, Len(Text$), @rc, #DT_LEFT | #DT_VCENTER | #DT_SINGLELINE)
          ;
          ; _________________________________________
          ;
          ; Gray the item if it is Disabled:
          ;
          If Disabled
            ; Item is grayed. Recover the drawing with a semi-transparent rectangle:
            If BackLuminosity > 128
              CoverColor = #White
            Else
              CoverColor = #Black
            EndIf
            rc = *drawItem\rcItem
            DrawTransparentRectangle(hDC, rc, CoverColor, 128)
          EndIf
          ;
          ProcedureReturn #True
          ;
        EndIf
      EndIf
  EndSelect
  ;
  ; Normal callback for all other messages:
  Protected SGI_OldCallBack = GetProp_(hWnd, "SGI_OldCallBack")
  ProcedureReturn CallWindowProc_(SGI_OldCallBack, hWnd, uMsg, wParam, lParam)
EndProcedure
;
Macro SGI_CreateGadget(OldNoGadget)
  If ODTType = #ODT_LISTBOX
    If OldNoGadget = -1
      NewNoGadget = ListViewGadget(#PB_Any, PosX, PosY, GWidth, GHeight, CheckType)
    Else
      ListViewGadget(OldNoGadget, PosX, PosY, GWidth, GHeight, CheckType)
      NewNoGadget = OldNoGadget
    EndIf
  ElseIf ODTType = #ODT_COMBOBOX
    If OldNoGadget = -1
      NewNoGadget = ComboBoxGadget(#PB_Any, PosX, PosY, GWidth, GHeight, CheckType)
    Else
      ComboBoxGadget(OldNoGadget, PosX, PosY, GWidth, GHeight, CheckType)
      NewNoGadget = OldNoGadget
    EndIf
  EndIf
EndMacro
;
Procedure SGI_Recreategadget(NoGadget)
  ;
  ; Check is the gadget has been created with OWNERDRAW option.
  ; If not, the gadget is destroyed and recreated with the good option.
  ; Data and items of the old gadget are transfered in the new one.
  ; The PureBasic gadget identifiant should not change during
  ; the operation, but the gadget handle (GadgetID(NoGadget))
  ; will be different.
  ;
  Protected ct, PosX, PosY, GWidth, GHeight, CheckType, NewNoGadget
  ;
  If IsGadget(NoGadget)
    Protected ODTType = SGI_GetODTTypeFromPBGadgetType(GadgetType(NoGadget))
    Protected currentStyle = GetWindowLongPtr_(GadgetID(NoGadget), #GWL_STYLE)
    ;
    If ODTType = #ODT_LISTBOX
      CheckType = #LBS_OWNERDRAWFIXED
    ElseIf ODTType = #ODT_COMBOBOX
      CheckType = #CBS_OWNERDRAWFIXED
    EndIf
    ;
    If CheckType And Not (currentStyle & CheckType)
      If ODTType = #ODT_COMBOBOX
        CheckType | #CBS_HASSTRINGS
      EndIf
      ; The gadget has been created without OWNERDRAWFIXED option.
      ; It is not possible to attribute this style after the creation,
      ; so, we must delete the gadget and recreate it with the right style.
      ;
      ; First store all what is possible to store about the gadget:
      NewList mGadgetItemText$()
      NewList mGadgetItemData()
      Protected mGadgetState = GetGadgetState(NoGadget)
      For ct = 0 To CountGadgetItems(NoGadget) - 1
        AddElement(mGadgetItemText$())
        AddElement(mGadgetItemData())
        mGadgetItemText$() = GetGadgetItemText(NoGadget, ct)
        mGadgetItemData() = GetGadgetItemData(NoGadget, ct)
      Next
      PosX = GadgetX(NoGadget) : PosY = GadgetY(NoGadget)
      GWidth = GadgetWidth(NoGadget) : GHeight = GadgetHeight(NoGadget)
      If ODTType = #ODT_COMBOBOX
        GHeight = SGI_GadgetsLineHeight + 6 ; Height parameter is fixed to (SGI_GadgetsLineHeight + 6) for ownerdrawn ComboBox, whatever the value used to create it.
      EndIf
      FreeGadget(NoGadget)
      ;
      ; Recreate the gadget:
      If NoGadget > 10000
        ; Gadget has been created using #PB_Any.
        ;
        ; Theorically, because the old gadget has just been deleted,
        ; the same number will be reattributed to the new one.
        SGI_CreateGadget(-1)
        If NewNoGadget <> NoGadget
          ; Gasp! It didn't work. Try to reuse the precedent number.
          FreeGadget(NewNoGadget)
          SGI_CreateGadget(NoGadget)
        EndIf
      Else
        ; Gadget has been created using a custom number. Keep it.
        SGI_CreateGadget(NoGadget)
      EndIf
      ;
      If ODTType = #ODT_LISTBOX
        SendMessage_(GadgetID(NoGadget), #LB_SETITEMHEIGHT, -1, SGI_GadgetsLineHeight)
      EndIf
      ;
      ; Reattribute the content, the data and the style of the deleted gadget:
      Protected NewStyle = GetWindowLongPtr_(GadgetID(NoGadget), #GWL_STYLE)
      SetWindowLongPtr_(GadgetID(NoGadget), #GWL_STYLE, NewStyle | currentStyle)
      ;
      ForEach mGadgetItemText$()
        AddGadgetItem(NoGadget, -1, mGadgetItemText$())
        SelectElement(mGadgetItemData(), ListIndex(mGadgetItemText$()))
        SetGadgetItemData(NoGadget, ListIndex(mGadgetItemText$()), mGadgetItemData())
      Next
      SetGadgetState(NoGadget, mGadgetState)
      ;
      ;Clean memory:
      FreeList(mGadgetItemText$())
      FreeList(mGadgetItemData())
    EndIf
  EndIf
EndProcedure
;
Procedure SGI_SetGadgetItemEx(GadgetNum, ItemNum)
  ;
  ; This is the main used procedure of this library.
  ; For each gadget item designed by 'GadgetNum' and 'ItemNum',
  ; it creates or find a record in the ItemSpecs() list.
  ;
  ; This record will be later used by the SGI_OwnerDrawnCallback procedure
  ; to draw the item with the requested font, icone and color.
  ;
  ; In addition to creating a registration in ItemSpecs(), this procedure
  ; also installs 'SGI_OwnerDrawnCallback()' as a callback procedure for
  ; the application's main window.
  ;
  ; For ComboBox and ListView gadgets, this procedure checks that they were created
  ; with the 'OwnerDrawn' option. When this is not the case, these gadgets are
  ; destroyed and recreated with the correct option.
  ;
  Protected Found = 0, MainWindowNum, SubMenu = 0, GadgetHandle, ItemPos, ResultType
  Protected mItemNum = ItemNum, ct
  ;
  ;
  If IsGadget(GadgetNum)
    Protected ODTType = SGI_GetODTTypeFromPBGadgetType(GadgetType(GadgetNum))
    GadgetHandle = GadgetID(GadgetNum)
    ;
    ; Retreive the main window PureBasic ID number:
    Protected MwID = GetAncestor_(GadgetHandle, #GA_ROOT)
    MainWindowNum = GetProp_(MwID, "PB_WINDOWID") - 1
    ;
  EndIf
  ;
  If ODTType
    If SGI_RetreiveItemSpecs(GadgetNum, ItemNum) = 0
      AddElement(ItemSpecs())
      ItemSpecs()\GadgetNum = GadgetNum
      ItemSpecs()\ItemPos = ItemNum
      ItemSpecs()\ODTType = ODTType
      ItemSpecs()\BackColor = -1
      ItemSpecs()\TextColor = -1
      ItemSpecs()\Font = -1
      ItemSpecs()\ImageNum = -1
      ItemSpecs()\ItemChecked = 0
      ;
      Protected SGI_OldCallBack = GetProp_(MwID, "SGI_OldCallBack")
      If SGI_OldCallBack = 0
        SGI_OldCallBack = SetWindowLongPtr_(MwID, #GWL_WNDPROC, @SGI_OwnerDrawnCallback())
        SetProp_(MwID, "SGI_OldCallBack"   , SGI_OldCallBack)
        SetProp_(MwID, "SGI_ActualCallBack", @SGI_OwnerDrawnCallback())
      EndIf
      ;
      SGI_Recreategadget(GadgetNum)
    EndIf
    ;
    ProcedureReturn #True
  Else
    ;
    ProcedureReturn #False
  EndIf
EndProcedure
;
;
;
; *****************************************************************************
;
;-                         4. LIBRARY FUNCTIONS
;
;          Functions added to the native set of PureBasic functions
;
; *****************************************************************************
;
;
Procedure SetGadgetItemFont(GadgetNum, ItemNum, Font)
  ;
  ; Allows to set a particular font to an item of ComboBox or
  ; ListView Gadget.
  ;
  If SGI_SetGadgetItemEx(GadgetNum, ItemNum)
    ItemSpecs()\Font = Font
    ProcedureReturn #True
  EndIf
EndProcedure
;
Procedure GetGadgetItemFont(GadgetNum, ItemNum)
  If SGI_RetreiveItemSpecs(GadgetNum, ItemNum)
    ProcedureReturn ItemSpecs()\Font
  EndIf
EndProcedure
;
Procedure CheckGadgetItem(GadgetNum, ItemNum, CheckUncheck)
  ;
  ; Allows to check or uncheck an item
  ; of a ComboBox or ListView Gadget.
  ;
  If SGI_SetGadgetItemEx(GadgetNum, ItemNum)
    ItemSpecs()\ItemChecked = CheckUncheck
    ProcedureReturn #True
  EndIf
EndProcedure
;
Procedure GetGadgetCheckedState(GadgetNum, ItemNum)
  If SGI_RetreiveItemSpecs(GadgetNum, ItemNum)
    ProcedureReturn ItemSpecs()\ItemChecked
  EndIf
EndProcedure
;
Procedure SetGadgetItemColorEx(GadgetNum, ItemNum, Colortype, Color, Colonne = 0)
  ;
  ; Allows to set a particular color to an item of ComboBox or
  ; ListView Gadget.
  ;
  ; This function complete the PureBasic native fonction 'SetGadgetItemColor()'
  ; which does not apply to ComboBox and ListView (until PB 6.20).
  ;
  If SGI_SetGadgetItemEx(GadgetNum, ItemNum)
    If Colortype = #PB_Gadget_BackColor
      ItemSpecs()\BackColor = Color
      ProcedureReturn #True
    ElseIf Colortype = #PB_Gadget_FrontColor
      ItemSpecs()\TextColor = Color
      ProcedureReturn #True
    EndIf
  Else
    ProcedureReturn SetGadgetItemColor(GadgetNum, ItemNum, Colortype, Colonne)
  EndIf
EndProcedure
;
Procedure GetGadgetItemColorEx(GadgetNum, ItemNum, Colortype, Colonne = 0)
  ;
  If SGI_RetreiveItemSpecs(GadgetNum, ItemNum)
    If Colortype = #PB_Gadget_BackColor
      ProcedureReturn ItemSpecs()\BackColor
    ElseIf Colortype = #PB_Gadget_FrontColor
      ProcedureReturn ItemSpecs()\TextColor
    EndIf
  Else
    ProcedureReturn GetGadgetItemColor(GadgetNum, ItemNum, Colortype, Colonne)
  EndIf
EndProcedure
;
Procedure SetGadgetItemImageEx(GadgetNum, ItemNum, *ItemImagePtr)
  ;
  ; Allows to attribute an image to an item of ComboBox or
  ; ListView Gadget.
  ;
  ; This function complete the PureBasic native fonction 'SetGadgetItemColor()'
  ; which does not apply to ListView (until PB 6.20).
  ;
  If IsImage(*ItemImagePtr)
    *ItemImagePtr = ImageID(*ItemImagePtr)
  EndIf
  ;
  If SGI_SetGadgetItemEx(GadgetNum, ItemNum)
    ;
    If IsImage(ItemSpecs()\ImageNum)
      FreeImage(ItemSpecs()\ImageNum)
    EndIf
    ItemSpecs()\ImageHandle = 0
    ;
    If *ItemImagePtr
      ItemSpecs()\ImageNum = ResizeImageToIconSize(*ItemImagePtr)
      If ItemSpecs()\ImageNum
        ItemSpecs()\ImageHandle = ImageID(ItemSpecs()\ImageNum)
        ProcedureReturn  #True
      EndIf
    EndIf
    ;
  Else
    SetGadgetItemImage(GadgetNum, ItemNum, *ItemImagePtr)
  EndIf
EndProcedure
;
Procedure GetGadgetItemImage(GadgetNum, ItemNum)
  If SGI_RetreiveItemSpecs(GadgetNum, ItemNum)
    ProcedureReturn ItemSpecs()\ImageHandle
  EndIf
EndProcedure
;
;
;
; *****************************************************************************
;
;-                5. OVERRIDING OF PUREBASIC NATIVE FUNCTIONS
;
; *****************************************************************************
;
Macro SetGadgetItemColor(GadgetNum, ItemNum, Colortype, Color, Colonne = 0)
  SetGadgetItemColorEx(GadgetNum, ItemNum, Colortype, Color, Colonne)
EndMacro
;
Macro GetGadgetItemColor(GadgetNum, ItemNum, Colortype, Colonne = 0)
  GetGadgetItemColorEx(GadgetNum, ItemNum, Colortype, Colonne)
EndMacro
;
Macro SetGadgetItemImage(GadgetNum, ItemNum, ItemImagePtr)
  SetGadgetItemImageEx(GadgetNum, ItemNum, ItemImagePtr)
EndMacro
;
; ********************************************************************************
;
;-                          6- DEMONSTRATION PROGRAM
;                 (just to see how it works and what it can do)
;
;*******************************************************************************
;
CompilerIf #PB_Compiler_IsMainFile
  ; The following won't run when this file is used as 'Included'.
  ;
  Define DemoWindow = OpenWindow(#PB_Any, 100, 100, 500, 365, "OwnerDraw ListView and ComboBox Gadgets", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  If DemoWindow
    ;
    Define CourierFont = LoadFont(#PB_Any, "Courier New", 9)
    Define SegoeFont = LoadFont(#PB_Any, "Segoe UI", 9)
    Define ComicFont = LoadFont(#PB_Any, "Comic Sans MS", 9)
    Define ImpactFont = LoadFont(#PB_Any, "Impact", 9)
    ;
    Define LightRedColor = RGB(255, 200, 200)
    Define DarkRedColor = RGB(200, 0, 0)
    Define GreenColor = RGB(200, 255, 200)
    Define BlueColor = RGB(200, 200, 255)
    Define YellowColor = RGB(255, 255, 0)
    ;
    Define img1 = GetImageFromShell32(13, 32, 32)
    Define img2 = GetImageFromShell32(43, 32, 32)
    Define img3 = GetImageFromShell32(22, 32, 32)
    ;
    ; ListView
    ;
    Define LVColor = ListViewGadget(#PB_Any, 10, 10, WindowWidth(DemoWindow) - 20, 80)
    ;
    AddGadgetItem(LVColor, 0, "LV Impact")
    SetGadgetItemImage(LVColor, 0, ImageID(img1))
    SetGadgetItemFont(LVColor, 0, ImpactFont)
    SetGadgetItemColor(LVColor, 0, #PB_Gadget_BackColor, LightRedColor)
    ;
    AddGadgetItem(LVColor, 1, "LV Courier New")
    SetGadgetItemImage(LVColor, 1, ImageID(img3))
    SetGadgetItemFont(LVColor, 1, CourierFont)
    SetGadgetItemColor(LVColor, 1, #PB_Gadget_BackColor, GreenColor)
    ;
    AddGadgetItem(LVColor, 2, "LV Comic Sans MS")
    CheckGadgetItem(LVColor, 2, #True)
    SetGadgetItemFont(LVColor, 2, ComicFont)
    SetGadgetItemColor(LVColor, 2, #PB_Gadget_BackColor, BlueColor)
    ;
    AddGadgetItem(LVColor, 3, "Standard")
    ;
    SetGadgetState(LVColor, 0)
    ;
    ; ComboBox
    ;
    Define VPos = 100
    Define CBColor = ComboBoxGadget(#PB_Any, 10, VPos, WindowWidth(DemoWindow) - 20, 0) ; <-- Height parameter is fixed to (SGI_GadgetsLineHeight + 6) for ownerdrawn ComboBox, whatever the value used to create it.
    ;
    AddGadgetItem(CBColor, 0, "CB Impact")
    SetGadgetItemFont(CBColor, 0, ImpactFont)
    SetGadgetItemColor(CBColor, 0, #PB_Gadget_BackColor, DarkRedColor)
    SetGadgetItemColor(CBColor, 0, #PB_Gadget_FrontColor, YellowColor)
    SetGadgetItemImage(CBColor, 0, ImageID(img2))
    ;
    AddGadgetItem(CBColor, 1, "Cb Courier New")
    SetGadgetItemFont(CBColor, 1, CourierFont)
    SetGadgetItemColor(CBColor, 1, #PB_Gadget_BackColor, GreenColor)
    SetGadgetItemImage(CBColor, 1, ImageID(img3))
    ;
    AddGadgetItem(CBColor, 2, "CB Comic Sans MS")
    SetGadgetItemFont(CBColor, 2, ComicFont)
    SetGadgetItemColor(CBColor, 2, #PB_Gadget_BackColor, BlueColor)
    CheckGadgetItem(CBColor, 2, #True)
    ;
    AddGadgetItem(CBColor, 3, "Standard")
    ;
    SetGadgetState(CBColor, 0)
    ;
    ;
    ;* ----------------------------- Buttons -----------------------------
    ;
    Define Msg$ = "By changing the value of SGI_ShowSelectionMethod, you change the manner"
    Msg$ + " of showing to the user that an item is selected. Usualy, a "
    Msg$ + "simple background shading is enough to do that, but it can be inefficient in a "
    Msg$ + "dark mode environment or when each item has its own particular color (or both). "
    Msg$ + "So, you can choose four different methods (see the SGI_ShowSelectionMethod enumeration)."
    Msg$ + " Try each of them to decide." + #CR$ + "Note that your can eventually combine different methods:"
    ;
    VPos + 40
    TextGadget(#PB_Any, 10, VPos, WindowWidth(DemoWindow) - 20, 100, Msg$, #PB_Text_Center)
    VPos + 95
    Define HPos = 40
    Define BulletButton = CheckBoxGadget(#PB_Any, HPos, VPos, 60, 25, "Bullet")
    HPos + 85
    Define BordersButton = CheckBoxGadget(#PB_Any, HPos, VPos, 60, 25, "Framing")
    HPos + 100
    Define SystemColorButton = CheckBoxGadget(#PB_Any, HPos, VPos, 90, 25, "SystemColor")
    HPos + 120
    Define ShadeButton = CheckBoxGadget(#PB_Any, HPos, VPos, 120, 25, "Shade background")
    ;
    SetGadgetState(SystemColorButton, #True)
    SetGadgetState(BulletButton, #True)
    SetGadgetState(BordersButton, #True)
    SGI_ShowSelectionMethod = #SGI_SystemColor | #SGI_Bullet | #SGI_Borders
    ;
    Msg$ = "This library requires no special skills to use. No initialization is needed. "
    Msg$ + "You don't need to install a callback procedure (this will be done automatically "
    Msg$ + "if necessary)." + #CR$
    Msg$ + "The only thing you need to do is include the library file in your program's code"
    Msg$ + " by using 'XIncludeFile (path)/SetGadgetItemEx.pbi'." + #CR$ +"From then on, you can call"
    Msg$ + " functions like 'SetGadgetItemColor()', 'SetGadgetItemFont()', 'SetGadgetItemImage()', etc."
    ;
    VPos + 30
    TextGadget(#PB_Any, 10, VPos, WindowWidth(DemoWindow) - 20, 100, Msg$, #PB_Text_Center)
    ;
    ; Main loop
    Repeat
      Select WaitWindowEvent()
        Case #PB_Event_Gadget
          Select EventGadget()
            Case BulletButton, BordersButton, SystemColorButton, ShadeButton
              SGI_ShowSelectionMethod = (GetGadgetState(BulletButton) * #SGI_Bullet)
              SGI_ShowSelectionMethod | (GetGadgetState(BordersButton) * #SGI_Borders)
              SGI_ShowSelectionMethod | (GetGadgetState(SystemColorButton) * #SGI_SystemColor)
              SGI_ShowSelectionMethod | (GetGadgetState(ShadeButton) * #SGI_ShadeBackground)
              RefreshGadget(LVColor)
              RefreshGadget(CBColor)
          EndSelect
        Case #PB_Event_CloseWindow
          Break
      EndSelect
    ForEver
    CloseWindow(DemoWindow)
  EndIf
CompilerEndIf