Listview header modificado en listview vbnet

Iniciado por Juan Sanchez, 8 Diciembre 2015, 20:06 PM

0 Miembros y 1 Visitante están viendo este tema.

Juan Sanchez

Saludos a todos los componentes de este foro, lo que quiero consultarles es como puedo cambiar el backcolor y forecolor en el header de un listview en vbnet con las apis de windows, se los agradeceré mucho y espero tener suerte...

Lekim

hola

No entiendo porqué quieres usar API para cambiar el estilo de las cabeceras de un Listview cuando vb.net ya ofrece medios para ello.

ListView.DrawColumnHeader Event



Código (vbnet) [Seleccionar]
Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ListView1.View = View.Details
        ListView1.Columns.Add("Header1", 100)
        ListView1.Columns.Add("Header2", 100)
        ListView1.Items.Add("Item1")
        ListView1.Items(0).SubItems.Add("SubItem1")
        ListView1.Items.Add("Item2")
        ListView1.Items(1).SubItems.Add("SubItem2")
        ListView1.OwnerDraw = True

    End Sub

    Private Sub ListView1_DrawColumnHeader(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewColumnHeaderEventArgs) Handles ListView1.DrawColumnHeader
        '//Rectángulos para el Bacground
        Try
            'Rectángulo inferior
            e.Graphics.FillRectangle(Brushes.Black, e.Bounds)
            'Rectángulo superior (Se superpone sobre el rectángulo anterior y es más pequeño)
            Dim RectPoint As New Point(e.Bounds.X + 1, e.Bounds.Y + 1)
            Dim RectSize As New Size(New Point(e.Bounds.Width - 2, e.Bounds.Height - 2))
            Dim Rect As New Rectangle(RectPoint, RectSize)
            e.Graphics.FillRectangle(Brushes.White, Rect)
        Finally
        End Try


        '//Dibuja el texto (Se superpone a los rectángulos anteriores o imagen de fondo)
        Dim sf As New StringFormat()
        Try
            Select Case e.Header.TextAlign
                Case HorizontalAlignment.Center
                    sf.Alignment = StringAlignment.Center
                Case HorizontalAlignment.Right
                    sf.Alignment = StringAlignment.Far
            End Select

            Dim headerFont As New Font("Helvetica", 10, FontStyle.Bold)
            Try
                e.Graphics.DrawString(e.Header.Text, headerFont, Brushes.Black, _
                                      New Point(e.Bounds.X + 5, e.Bounds.Y + 1), sf)
            Finally
                headerFont.Dispose()
            End Try

        Finally
            sf.Dispose()
        End Try

     
    End Sub

    Private Sub ListView1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewItemEventArgs) Handles ListView1.DrawItem
        e.DrawDefault = True
    End Sub
End Class


Los cambios se asignan por capas primero estableces lo que sería el 'Background' mediante FillRectangle, si creas otro FillRectangle éste se coloca en la parte superior del anterior en el mismo orden en el que se nombra en el código. Y por último la representación del texto. Si escribes la referéncia al texto antes que aplicar el rectángulo entonces no se verá el texto.

También puedes aplicar una imagen en lugar FillRectangle:

Código (vbnet) [Seleccionar]
       
   Public Class Form1
    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        ListView1.View = View.Details
        ListView1.Columns.Add("Header1", 100)
        ListView1.Columns.Add("Header2", 100)
        ListView1.Items.Add("Item1")
        ListView1.Items(0).SubItems.Add("SubItem1")
        ListView1.Items.Add("Item2")
        ListView1.Items(1).SubItems.Add("SubItem2")
        ListView1.OwnerDraw = True

    End Sub

    Private Sub ListView1_DrawColumnHeader(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewColumnHeaderEventArgs) Handles ListView1.DrawColumnHeader
     
        '//Dibuja una imagen
        Dim imagen As Image
        imagen = Image.FromFile("C:\Documents and Settings\Administrador\Mis documentos\columnHeader.png")
        e.Graphics.DrawImage(imagen, e.Bounds)


        '//Dibuja el texto (Se superpone a los rectángulos anteriores o imagen de fondo)
        Dim sf As New StringFormat()
        Try
            Select Case e.Header.TextAlign
                Case HorizontalAlignment.Center
                    sf.Alignment = StringAlignment.Center
                Case HorizontalAlignment.Right
                    sf.Alignment = StringAlignment.Far
            End Select

            Dim headerFont As New Font("Helvetica", 10, FontStyle.Bold)
            Try
                e.Graphics.DrawString(e.Header.Text, headerFont, Brushes.Black, _
                                      New Point(e.Bounds.X + 5, e.Bounds.Y + 1), sf)
            Finally
                headerFont.Dispose()
            End Try

        Finally
            sf.Dispose()
        End Try

     
    End Sub

    Private Sub ListView1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewItemEventArgs) Handles ListView1.DrawItem
        e.DrawDefault = True 'No quitar esto. A no ser que apliques una personalización para este evento. DrawDefault dibujará los valores por defecto.
    End Sub
End Class




Juan Sanchez

#2
Gracias Lekim por responder a la pregunta, lo que quisiera saber como cambiar el backcolor en el header de la parte derecha del sobrante, es decir que toda la barra del header quede de un solo color.
lo que quiero hacer es que se cambie el backcolor de otro color sin utilizar el evento DrawColumnHeader y sin perder los efectos cuando el mouse pasa por encima, por eso decía si se puede modificar solo el backcolor usando alguna api de windows para eso.

tengo mi código que es lo siguiente, lo repinta toda la barra del header pero lo borra las columnas existentes.

Librerias:
Código (vbnet) [Seleccionar]
Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWndParent As IntPtr, ByVal hwndChildAfter As IntPtr, ByVal lpszClass As String, ByVal lpszWindow As String) As IntPtr
 Private Declare Function GetClientRect Lib "user32.dll" (ByVal hwnd As IntPtr, ByRef lpRect As RECT) As Integer
 Private Declare Function FillRect Lib "USER32.DLL" (ByVal hDC As Integer, ByRef lpRC As RECT, ByVal hBR As Integer) As Integer
 Private Declare Function CreateSolidBrush Lib "GDI32.DLL" (ByVal crColor As Integer) As Integer
 Private Declare Function DeleteObject Lib "GDI32.DLL" (ByVal hObject As Integer) As Integer
 Private Declare Function GetDC Lib "user32" (ByVal hwnd As IntPtr) As Integer
 Private Structure RECT
   Dim Left As Integer
   Dim Top As Integer
   Dim Right As Integer
   Dim Bottom As Integer
 End Structure

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
   Dim m_HdrHwnd As IntPtr, x As IntPtr, rt As RECT, hBrush As Integer, dc As Integer
   m_HdrHwnd = FindWindowEx(ListView1.Handle, 0, "SysHeader32", vbNullString)
   x = GetClientRect(m_HdrHwnd, rt)
   dc = GetDC(m_HdrHwnd)
   hBrush = CreateSolidBrush(14563858)
   FillRect(dc, rt, hBrush)
 End Sub



mas o menos quiero algo así como en esta imagen en el siguiente link



Gracias por responderme.
 

Eleкtro

#3
Es una tarea bastante complicada, incluso recurriendo a P/invokes de la WinAPI.

La zona "sin utilizar" en la derecha de la cabecera de las columnas del Listview forma parte del área NO-cliente de la ventana del control, por ende usando código administrado por ejemplo en el evento DrawColumnHeader solo vas a poder dibujar sobre el área cliente del control, así que tienes que recurrir si o si a las funciones Win32 relacionadas con GDI.

Pero el resultado será sencillamente imperfecto, ya que al tratarse del área no-cliente y como lo estamos tratando de pintar manualmente, Windows no puede determinar cuando se debe redibujar ese área, así que a veces verás como desaparece el color de ese área, ya que con los métodos base de invocadores de eventos del ListView parece no ser suficiente para indicar manualmente cuando se debe redibujar el área no-cliente en los eventos que se suceden en el control (ya que no hay un invocador de evento para controlar cuando se hace click en un separador de columna).

Aquí tienes un ejemplo que escribí, tomatelo como tal...un ejemplo, por que como ya dije esto es imperfecto, notarás como desaparece el color al hacer click sobre un separador de columna, y al redimensionar las columnas;
una solución a lo mencionado sería determinar los handles de ventana (hWnd) de todos los separadores de columnas que existan en el control, y suscribirte a cada una de las ventanas de los separadores, es decir a la función de ventana que procesa sus mensajes (WndProc) para determinar cuando se hace click (mensaje de ventana: WM_LButtonUp y WM_NcLButtonUp ) o cuando se mueve (mensaje de ventana: WM_Move y WM_ExitSizeMove) y en ese momento pintar el area no cliente, pero hacer todo eso es demasiado costoso para satisfacer un simple capricho de pintar esa zona... y aparte, por naturaleza este tipo de hacks en los controles de WinForms tenderán a generar molestos efectos de Flickering.

Código (vbnet) [Seleccionar]
Imports System
Imports System.ComponentModel
Imports System.Diagnostics
Imports System.Drawing
Imports System.Linq
Imports System.Runtime.InteropServices
Imports System.Security
Imports System.Windows.Forms

Public Class ListViewEx : Inherits ListView

   <Category("Appearance")>
   <DisplayName("Column-Header BackColor")>
   Public Property ColumnHeaderBackColor As Color

   <Category("Appearance")>
   <DisplayName("Column-Header ForeColor")>
   Public Property ColumnHeaderForeColor As Color

   <Category("Appearance")>
   <DisplayName("Column-Header Separator Color")>
   Public Property ColumnHeaderSeparatorColor As Color

   <Category("Appearance")>
   <DisplayName("Column-Header NonClient-Area Drawing Enabled")>
   Public Property ColumnHeaderNonClientAreaDrawingEnabled As Boolean

   Private lastColumnHeaderDrawEventArgs As DrawListViewColumnHeaderEventArgs
   Private ignoreColumnHeaderNonClientAreaDrawing As Boolean

   <DebuggerNonUserCode>
   Public Sub New()

       Me.ColumnHeaderSeparatorColor = SystemPens.ControlLightLight.Color
       Me.ColumnHeaderBackColor = SystemPens.ControlDarkDark.Color
       Me.ColumnHeaderForeColor = SystemColors.ControlText

       MyBase.OwnerDraw = True

   End Sub

   Protected Overrides Sub OnEnter(e As EventArgs)
       Me.DrawNonClientColumnHeader(Me.lastColumnHeaderDrawEventArgs)
       MyBase.OnEnter(e)
   End Sub

   Protected Overrides Sub OnLeave(e As EventArgs)
       Me.DrawNonClientColumnHeader(Me.lastColumnHeaderDrawEventArgs)
       MyBase.OnLeave(e)
   End Sub

   Protected Overrides Sub OnGotFocus(e As EventArgs)
       Me.DrawNonClientColumnHeader(Me.lastColumnHeaderDrawEventArgs)
       MyBase.OnGotFocus(e)
   End Sub

   Protected Overrides Sub OnLostFocus(e As EventArgs)
       Me.DrawNonClientColumnHeader(Me.lastColumnHeaderDrawEventArgs)
       MyBase.OnLostFocus(e)
   End Sub

   Protected Overrides Sub OnInvalidated(e As InvalidateEventArgs)
       Me.DrawNonClientColumnHeader(Me.lastColumnHeaderDrawEventArgs)
       MyBase.OnInvalidated(e)
   End Sub

   Protected Overrides Sub OnDrawSubItem(e As DrawListViewSubItemEventArgs)
       Me.DrawNonClientColumnHeader(Me.lastColumnHeaderDrawEventArgs)
       MyBase.OnDrawSubItem(e)
   End Sub

   Protected Overrides Sub OnColumnWidthChanging(e As ColumnWidthChangingEventArgs)
       Me.ignoreColumnHeaderNonClientAreaDrawing = True
       MyBase.OnColumnWidthChanging(e)
   End Sub

   Protected Overrides Sub OnColumnWidthChanged(e As ColumnWidthChangedEventArgs)
       Me.ignoreColumnHeaderNonClientAreaDrawing = False
       Me.DrawNonClientColumnHeader(Me.lastColumnHeaderDrawEventArgs)
       MyBase.OnColumnWidthChanged(e)
   End Sub

   Protected Overrides Sub OnDrawColumnHeader(ByVal e As DrawListViewColumnHeaderEventArgs)
       Me.DrawClientColumnHeader(e)
       MyBase.OnDrawColumnHeader(e)
   End Sub

   Protected Overrides Sub OnDrawItem(e As DrawListViewItemEventArgs)

       Using g As Graphics = e.Graphics
           e.DrawText()
       End Using

       MyBase.OnDrawItem(e)

   End Sub

   <DebuggerStepThrough>
   Private Sub DrawClientColumnHeader(ByVal e As DrawListViewColumnHeaderEventArgs)

       ' If it is the last column...
       If (e.ColumnIndex = (MyBase.Columns.Count - 1)) Then
           Me.lastColumnHeaderDrawEventArgs = e
           Me.DrawNonClientColumnHeader(e)
       End If

       e.DrawDefault = False ' Set Owner drawing.

       Using g As Graphics = e.Graphics

           ' Draw header background.
           Dim bgBounds As Rectangle = e.Bounds
           Using bgBrush As New SolidBrush(Me.ColumnHeaderBackColor)
               g.FillRectangle(bgBrush, bgBounds)
           End Using

           ' Measure column separator bounds.
           Dim colBounds As Rectangle = e.Bounds
           colBounds.Width -= 1
           colBounds.Height -= 1

           ' Draw column separator.
           Using separatorPen As New Pen(Me.ColumnHeaderSeparatorColor)
               g.DrawRectangle(separatorPen, colBounds)
           End Using

           ' Set text formatting.
           Dim flags As TextFormatFlags = TextFormatFlags.VerticalCenter Or TextFormatFlags.EndEllipsis
           Select Case e.Header.TextAlign
               Case HorizontalAlignment.Left
                   flags = (flags Or TextFormatFlags.Left)
               Case HorizontalAlignment.Center
                   flags = (flags Or TextFormatFlags.HorizontalCenter)
               Case Else
                   flags = (flags Or TextFormatFlags.Right)
           End Select

           ' Measure and draw text.
           Dim width As Integer = TextRenderer.MeasureText(" ", e.Font).Width
           Dim txtBounds As Rectangle = Rectangle.Inflate(e.Bounds, -width, 0)
           TextRenderer.DrawText(g, e.Header.Text, e.Font, txtBounds, Me.ColumnHeaderForeColor, flags)

       End Using

   End Sub

   <DebuggerStepThrough>
   Private Sub DrawNonClientColumnHeader(ByVal e As DrawListViewColumnHeaderEventArgs)

       If (Me.ColumnHeaderNonClientAreaDrawingEnabled) AndAlso Not (Me.ignoreColumnHeaderNonClientAreaDrawing) AndAlso
          (e IsNot Nothing) Then

           Dim lv As ListView = e.Header.ListView
           Dim hwnd As IntPtr = Me.GetColumnHeaderHwnd(lv)
           Dim hdc As IntPtr = NativeMethods.GetDC(hwnd)
           Dim headersWidth As Integer =
               (From col As ColumnHeader In MyBase.Columns.Cast(Of ColumnHeader)()
                Select col.Width
               ).Sum

           Using g As Graphics = Graphics.FromHdc(hdc)

               Dim rect As New Rectangle(e.Bounds.Right + 1, e.Bounds.Top, (Me.Bounds.Width - headersWidth), e.Bounds.Height)

               Using bgBrush As New SolidBrush(Me.ColumnHeaderBackColor)
                   g.FillRectangle(bgBrush, rect)
               End Using

           End Using

           NativeMethods.ReleaseDC(hwnd, hdc)

       End If

   End Sub

   <DebuggerStepThrough>
   Private Function GetColumnHeaderHwnd(ByVal lv As ListView) As IntPtr
       Return NativeMethods.SendMessage(lv.Handle, NativeMethods.LvmGetHeader, New IntPtr(0), New IntPtr(0))
   End Function

End Class

Friend NotInheritable Class NativeMethods

   ' https://msdn.microsoft.com/en-us/library/windows/desktop/bb774937%28v=vs.85%29.aspx
   Friend Const LvmGetHeader As Integer = &H101F

   ' https://msdn.microsoft.com/en-us/library/windows/desktop/dd144871(v=vs.85).aspx
   <SuppressUnmanagedCodeSecurity>
   <DllImport("user32", SetLastError:=True)>
   Friend Shared Function GetDC(
          ByVal hwnd As IntPtr
   ) As IntPtr
   End Function

   ' https://msdn.microsoft.com/en-us/library/windows/desktop/dd162920(v=vs.85).aspx
   <SuppressUnmanagedCodeSecurity>
   <DllImport("user32", SetLastError:=True)>
   Friend Shared Function ReleaseDC(
          ByVal hwnd As IntPtr,
          ByVal hdc As IntPtr
   ) As IntPtr
   End Function

   ' https://msdn.microsoft.com/en-us/library/windows/desktop/ms644950(v=vs.85).aspx
   <SuppressUnmanagedCodeSecurity>
   <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
   Friend Shared Function SendMessage(
          ByVal hwnd As IntPtr,
          ByVal msg As UInteger,
          ByVal wParam As IntPtr,
          ByVal lParam As IntPtr
   ) As IntPtr
   End Function

End Class


PD: Nótese que me faltó añadir un Getter/Setter a las propiedades para llamar a los métodos que pintan sobre la cabecera de las columnas cuando se modifica el valor de dichas propiedades.

Saludos