c# - Change color of groups in ObjectListView
Asked Answered
G

2

6

When building groups in ObjectListView, how do I change the color of the group? In default groups are shown with a dark blue line in dark blue forecolor. How can I change it?

Garlinda answered 21/9, 2015 at 16:55 Comment(0)
T
2

Its apparently not possible. There was a discussion about the ability to change the group header font/style several years ago. I don't know if that still represents the actual situation, but I wasn't able to come up with a solution when I was looking for it several weeks ago.

Even using the undocumented ListView API's, there is no mechanism to change anything about how a group header is rendered. You can't change the font, color, background color, anything.

Well, under XP only, you can change the color of the group header (via the SetGroupMetrics message). But under Vista and later, that ability was removed.

Trapezohedron answered 22/9, 2015 at 6:1 Comment(1)
there is a "workaround".. You can make your own opaque overlay, with some clever programming and checking the locations and heights of the headers and all objects you can draw your own groups.. The clicks go straight through to the actual group bar too. However when I made the proof of concept code for that it made a lot of assumptions and was imperfect.Intromit
S
0

You can achieve this with a regular winforms Listview as below:

Imports System.ComponentModel
Imports System.Drawing
Imports System.Drawing.Drawing2D
Imports System.Runtime.InteropServices
Imports System.Windows.Forms

<ToolboxItem(False)>
Public Class ListViewExt
    Inherits ListView

    Private _groupHeadingBackColor As Color = Color.Gray
    Public Property GroupHeadingBackColor() As Color
        Get
            Return _groupHeadingBackColor
        End Get
        Set(ByVal value As Color)
            _groupHeadingBackColor = value
        End Set
    End Property

    Private _groupHeadingForeColor As Color = Color.Black
    Public Property GroupHeadingForeColor() As Color
        Get
            Return _groupHeadingForeColor
        End Get
        Set(ByVal value As Color)
            _groupHeadingForeColor = value
        End Set
    End Property

    Private _groupHeadingFont As Font = Me.Font
    Public Property GroupHeadingFont() As Font
        Get
            Return _groupHeadingFont
        End Get
        Set(ByVal value As Font)
            _groupHeadingFont = value
        End Set
    End Property

    Private _separatorColor As Color
    Public Property SeparatorColor() As Color
        Get
            Return _separatorColor
        End Get
        Set(ByVal value As Color)
            _separatorColor = value
        End Set
    End Property

    Public Const LVCDI_ITEM = &H0
    Public Const LVCDI_GROUP = &H1
    Public Const LVCDI_ITEMSLIST = &H2

    Public Const LVM_FIRST = &H1000
    Public Const LVM_GETGROUPRECT = (LVM_FIRST + 98)
    Public Const LVM_ENABLEGROUPVIEW = (LVM_FIRST + 157)
    Public Const LVM_SETGROUPINFO = (LVM_FIRST + 147)
    Public Const LVM_GETGROUPINFO = (LVM_FIRST + 149)
    Public Const LVM_REMOVEGROUP = (LVM_FIRST + 150)
    Public Const LVM_MOVEGROUP = (LVM_FIRST + 151)
    Public Const LVM_GETGROUPCOUNT = (LVM_FIRST + 152)
    Public Const LVM_GETGROUPINFOBYINDEX = (LVM_FIRST + 153)
    Public Const LVM_MOVEITEMTOGROUP = (LVM_FIRST + 154)

    Public Const WM_LBUTTONUP = &H202

    <StructLayout(LayoutKind.Sequential)>
    Public Structure NMHDR
        Public hwndFrom As IntPtr
        Public idFrom As IntPtr
        Public code As Integer
    End Structure

    <StructLayout(LayoutKind.Sequential)>
    Public Structure RECT
        Public left As Integer
        Public top As Integer
        Public right As Integer
        Public bottom As Integer
    End Structure

    <StructLayout(LayoutKind.Sequential)>
    Public Structure NMCUSTOMDRAW
        Public hdr As NMHDR
        Public dwDrawStage As Integer
        Public hdc As IntPtr
        Public rc As RECT
        Public dwItemSpec As IntPtr
        Public uItemState As UInteger
        Public lItemlParam As IntPtr
    End Structure

    <StructLayout(LayoutKind.Sequential)>
    Public Structure NMLVCUSTOMDRAW
        Public nmcd As NMCUSTOMDRAW
        Public clrText As Integer
        Public clrTextBk As Integer
        Public iSubItem As Integer
        Public dwItemType As Integer
        Public clrFace As Integer
        Public iIconEffect As Integer
        Public iIconPhase As Integer
        Public iPartId As Integer
        Public iStateId As Integer
        Public rcText As RECT
        Public uAlign As UInteger
    End Structure

    <StructLayout(LayoutKind.Sequential, CharSet:=CharSet.Unicode)>
    Public Structure LVGROUP
        Public cbSize As UInteger
        Public mask As UInteger
        '<MarshalAs(UnmanagedType.LPTStr)>
        'Public pszHeader As String
        Public pszHeader As IntPtr
        Public cchHeader As Integer
        '<MarshalAs(UnmanagedType.LPTStr)>
        'Public pszFooter As String
        Public pszFooter As IntPtr
        Public cchFooter As Integer
        Public iGroupId As Integer
        Public stateMask As UInteger
        Public state As UInteger
        Public uAlign As UInteger

        '<MarshalAs(UnmanagedType.LPTStr)>
        'Public pszSubtitle As String
        Public pszSubtitle As IntPtr
        Public cchSubtitle As UInteger
        '<MarshalAs(UnmanagedType.LPTStr)>
        'Public pszTask As String
        Public pszTask As IntPtr
        Public cchTask As UInteger
        '<MarshalAs(UnmanagedType.LPTStr)>
        'Public pszDescriptionTop As String
        Public pszDescriptionTop As IntPtr
        Public cchDescriptionTop As UInteger
        '<MarshalAs(UnmanagedType.LPTStr)>
        'Public pszDescriptionBottom As String
        Public pszDescriptionBottom As IntPtr
        Public cchDescriptionBottom As UInteger
        Public iTitleImage As Integer
        Public iExtendedImage As Integer
        Public iFirstItem As Integer
        Public cItems As UInteger
        '<MarshalAs(UnmanagedType.LPTStr)>
        'Public pszSubsetTitle As String
        Public pszSubsetTitle As IntPtr
        Public cchSubsetTitle As UInteger
    End Structure


    <Flags>
    Public Enum CDRF
        CDRF_DODEFAULT = &H0
        CDRF_NEWFONT = &H2
        CDRF_SKIPDEFAULT = &H4
        CDRF_DOERASE = &H8
        CDRF_SKIPPOSTPAINT = &H100
        CDRF_NOTIFYPOSTPAINT = &H10
        CDRF_NOTIFYITEMDRAW = &H20
        CDRF_NOTIFYSUBITEMDRAW = &H20
        CDRF_NOTIFYPOSTERASE = &H40
    End Enum

    <Flags>
    Public Enum CDDS
        CDDS_PREPAINT = &H1
        CDDS_POSTPAINT = &H2
        CDDS_PREERASE = &H3
        CDDS_POSTERASE = &H4
        CDDS_ITEM = &H10000
        CDDS_ITEMPREPAINT = (CDDS_ITEM Or CDDS_PREPAINT)
        CDDS_ITEMPOSTPAINT = (CDDS_ITEM Or CDDS_POSTPAINT)
        CDDS_ITEMPREERASE = (CDDS_ITEM Or CDDS_PREERASE)
        CDDS_ITEMPOSTERASE = (CDDS_ITEM Or CDDS_POSTERASE)
        CDDS_SUBITEM = &H20000
    End Enum

    Public Const LVGF_NONE = &H0
    Public Const LVGF_HEADER = &H1
    Public Const LVGF_FOOTER = &H2
    Public Const LVGF_STATE = &H4
    Public Const LVGF_ALIGN = &H8
    Public Const LVGF_GROUPID = &H10

    Public Const LVGF_SUBTITLE = &H100 'pszSubtitle is valid
    Public Const LVGF_TASK = &H200 'pszTask is valid
    Public Const LVGF_DESCRIPTIONTOP = &H400 'pszDescriptionTop is valid
    Public Const LVGF_DESCRIPTIONBOTTOM = &H800 'pszDescriptionBottom is valid
    Public Const LVGF_TITLEIMAGE = &H1000 'iTitleImage is valid
    Public Const LVGF_EXTENDEDIMAGE = &H2000 'iExtendedImage is valid
    Public Const LVGF_ITEMS = &H4000 'iFirstItem and cItems are valid
    Public Const LVGF_SUBSET = &H8000 'pszSubsetTitle is valid
    Public Const LVGF_SUBSETITEMS = &H10000 'readonly, cItems holds count of items in visible subset, iFirstItem is valid

    Public Const LVGS_NORMAL = &H0
    Public Const LVGS_COLLAPSED = &H1
    Public Const LVGS_HIDDEN = &H2
    Public Const LVGS_NOHEADER = &H4
    Public Const LVGS_COLLAPSIBLE = &H8
    Public Const LVGS_FOCUSED = &H10
    Public Const LVGS_SELECTED = &H20
    Public Const LVGS_SUBSETED = &H40
    Public Const LVGS_SUBSETLINKFOCUSED = &H80

    Public Const LVGA_HEADER_LEFT = &H1
    Public Const LVGA_HEADER_CENTER = &H2
    Public Const LVGA_HEADER_RIGHT = &H4 'Don't forget to validate exclusivity
    Public Const LVGA_FOOTER_LEFT = &H8
    Public Const LVGA_FOOTER_CENTER = &H10
    Public Const LVGA_FOOTER_RIGHT = &H20 'Don't forget to validate exclusivity

    Public Const LVGGR_GROUP = 0 ' Entire expanded group
    Public Const LVGGR_HEADER = 1  ' Header only (collapsed group)
    Public Const LVGGR_LABEL = 2  ' Label only
    Public Const LVGGR_SUBSETLINK = 3  'subset link only

    <DllImport("User32.dll", EntryPoint:="SendMessageW", SetLastError:=True)>
    Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByRef lParam As IntPtr) As Integer
    End Function

    <DllImport("User32.dll", EntryPoint:="SendMessageW", SetLastError:=True)>
    Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByRef lParam As LVGROUP) As Integer
    End Function

    <DllImport("User32.dll", EntryPoint:="SendMessageW", SetLastError:=True)>
    Public Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByRef lParam As RECT) As Integer
    End Function

    <DllImport("User32.dll", EntryPoint:="PostMessageW", SetLastError:=True)>
    Public Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As Integer, ByVal wParam As Integer, ByRef lParam As IntPtr) As Integer
    End Function

    Public Function SetGroupInfo(ByVal hWnd As IntPtr, nGroupID As Integer, nSate As UInteger) As Integer
        Dim lvg As LVGROUP = New LVGROUP()
        lvg.cbSize = CUInt(Marshal.SizeOf(lvg))
        lvg.mask = LVGF_STATE Or LVGF_GROUPID Or LVGF_HEADER
        ' for test
        Dim nRet2 As Integer = SendMessage(hWnd, LVM_GETGROUPINFO, nGroupID, lvg)

        lvg.state = nSate
        lvg.mask = LVGF_STATE
        nRet2 = SendMessage(hWnd, LVM_SETGROUPINFO, nGroupID, lvg)
        Return -1
    End Function

    Protected Overrides Sub WndProc(ByRef m As Message)
        If m.Msg = WM_REFLECT + WM_NOFITY Then
            Dim pnmhdr = CType(m.GetLParam(GetType(NMHDR)), NMHDR)
            If pnmhdr.code = NM_CUSTOMDRAW Then
                Dim pnmlv = CType(m.GetLParam(GetType(NMLVCUSTOMDRAW)), NMLVCUSTOMDRAW)
                Select Case pnmlv.nmcd.dwDrawStage
                    Case CDDS.CDDS_PREPAINT
                        If (pnmlv.dwItemType = LVCDI_GROUP) Then
                            Dim rectHeader As RECT = New RECT()
                            rectHeader.top = LVGGR_HEADER
                            Dim nItem As Integer = CInt(pnmlv.nmcd.dwItemSpec)
                            'If (nItem = 0) Then
                            Dim nRet As Integer = SendMessage(m.HWnd, LVM_GETGROUPRECT, nItem, rectHeader)
                            Using g As Graphics = Graphics.FromHdc(pnmlv.nmcd.hdc)


                                Dim rect As New Rectangle(rectHeader.left, rectHeader.top, rectHeader.right - rectHeader.left, rectHeader.bottom - rectHeader.top)

                                'Dim linGrBrush As New LinearGradientBrush(New System.Drawing.Point(0, 0), New System.Drawing.Point(rectHeader.right, rectHeader.bottom), Color.Blue, Color.LightCyan)
                                Dim BgBrush As New SolidBrush(_groupHeadingBackColor)
                                g.FillRectangle(BgBrush, rect)

                                Dim lvg As LVGROUP = New LVGROUP()
                                lvg.cbSize = CUInt(Marshal.SizeOf(lvg))
                                lvg.mask = LVGF_STATE Or LVGF_GROUPID Or LVGF_HEADER
                                Dim nRet2 As Integer = SendMessage(m.HWnd, LVM_GETGROUPINFO, nItem, lvg)
                                Dim sText = Marshal.PtrToStringUni(lvg.pszHeader)

                                Dim textSize As SizeF = g.MeasureString(sText, _groupHeadingFont)

                                Dim RectHeightMiddle As Integer = CInt((rect.Height - textSize.Height) / 2)

                                rect.Offset(10, RectHeightMiddle)

                                Using drawBrush As New SolidBrush(_groupHeadingForeColor)

                                    g.DrawString(sText, _groupHeadingFont, drawBrush, rect)
                                    rect.Offset(0, -RectHeightMiddle)

                                    Using lineBrush As New SolidBrush(_separatorColor)
                                        g.DrawLine(New Pen(lineBrush), rect.X + g.MeasureString(sText, _groupHeadingFont).Width + 10,
                                                                    rect.Y + CInt(rect.Height / 2),
                                                                    rect.X + CInt(rect.Width * 95 / 100),
                                                                    rect.Y + CInt(rect.Height / 2))

                                    End Using
                                End Using
                            End Using
                            m.Result = New IntPtr(CDRF.CDRF_SKIPDEFAULT)
                            'End If
                        Else
                            m.Result = New IntPtr(CDRF.CDRF_NOTIFYITEMDRAW)
                        End If
                    Case CDDS.CDDS_ITEMPREPAINT
                        m.Result = New IntPtr(CDRF.CDRF_NOTIFYSUBITEMDRAW Or CDRF.CDRF_NOTIFYPOSTPAINT)
                    Case CDDS.CDDS_ITEMPOSTPAINT
                End Select
            End If
            Return
        Else
            MyBase.WndProc(m)
        End If
    End Sub

    Private Const NM_FIRST As Integer = 0
    Private Const NM_CLICK As Integer = NM_FIRST - 2
    Private Const NM_CUSTOMDRAW As Integer = NM_FIRST - 12
    Private Const WM_REFLECT As Integer = &H2000
    Private Const WM_NOFITY As Integer = &H4E
End Class
Sholom answered 6/2, 2022 at 16:14 Comment(3)
Answering about the wrong control in the wrong language?Intromit
A bit pedantic. My answer provides a means by which to achieve what the OP was requesting (albeit via ListView rather than that particular control). Granted it's in vb.net but any monkey can convert. Do you just toddle about randomly picking holes in people's attempts to help? Wtg - keep up the good positivity.Sholom
yeah the diff language doesnt matter much but an ObjectListView is a specific ocntrol that operates completely differently than a Listview, even though it uses a Listview as an underlying control.. I havnt tested but this is unlikely to work, Ive worked with the OLV in a few projects and you cannot use it like a regular ListView. Adding on to that, it being in the wrong language just kinda complicates things..Intromit

© 2022 - 2024 — McMap. All rights reserved.