﻿Option Explicit On
Option Strict On
Option Infer Off

Imports System.ComponentModel

<System.Diagnostics.DebuggerStepThrough()> _
Public Class MultilineListbox
    Inherits System.Windows.Forms.ListBox

#Region " Variables "

    Private m_DrawDivider, m_MultilineEnabled As Boolean
    Private m_BackBrush, m_ForeBrush As SolidBrush
    Private m_DividerColor As Color
    Private m_DividerThickness As Single
    Private m_DividerPen As Pen

    Public Event MultilineEnabledChanged As EventHandler

#End Region
#Region " Constructor, Dispose "

    Public Sub New()
        MyBase.DrawMode = Windows.Forms.DrawMode.OwnerDrawVariable
        Me.DividerThickness = 1.0!
        Me.DividerColor = Me.ForeColor
        Me.DrawDivider = True
        Me.MultilineEnabled = True
    End Sub

    Protected Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            'The object is being explicitly disposed so dispose its children.
            If m_BackBrush IsNot Nothing Then m_BackBrush.Dispose()
            If m_ForeBrush IsNot Nothing Then m_ForeBrush.Dispose()
        End If
        m_BackBrush = Nothing
        m_ForeBrush = Nothing
        MyBase.Dispose(disposing)
    End Sub

#End Region
#Region " Overrides: OnDrawItem, OnMeasureItem "

    Protected Overrides Sub OnDrawItem(ByVal e As System.Windows.Forms.DrawItemEventArgs)
        e.DrawBackground()
        e.DrawFocusRectangle()
        If Me.Items.Count > 0I Then
            Try
                Dim isSelected As Boolean = (e.State And DrawItemState.Selected) = DrawItemState.Selected

                m_BackBrush = New SolidBrush(If(isSelected, SystemColors.Highlight, Me.BackColor))
                m_ForeBrush = New SolidBrush(If(isSelected, SystemColors.HighlightText, Me.ForeColor))

                e.Graphics.FillRectangle(m_BackBrush, e.Bounds)
                e.Graphics.DrawString(Me.GetItemText(Me.Items(e.Index)), Me.Font, m_ForeBrush, e.Bounds.X, e.Bounds.Y)

                If m_DrawDivider AndAlso e.Index < Me.Items.Count - 1I AndAlso Not isSelected Then e.Graphics.DrawLine(m_DividerPen, 0I, e.Bounds.Bottom - 1I, Me.Width, e.Bounds.Bottom - 1I)
            Catch : End Try
        End If
        MyBase.OnDrawItem(e)
    End Sub

    Protected Overrides Sub OnMeasureItem(ByVal e As System.Windows.Forms.MeasureItemEventArgs)
        If m_MultilineEnabled AndAlso Me.Items.Count > 0I Then
            e.ItemHeight = CInt(e.Graphics.MeasureString(Me.GetItemText(Me.Items(e.Index)), Me.Font).Height + m_DividerThickness + 0.499!)
        End If
        MyBase.OnMeasureItem(e)
    End Sub

#End Region
#Region " Shadows: IndexFromPoint "

    Public Shadows Function IndexFromPoint(ByVal Location As Point) As Integer
        Return Me.IndexFromPoint(Location.X, Location.Y)
    End Function

    Public Shadows Function IndexFromPoint(ByVal X As Integer, ByVal Y As Integer) As Integer
        Dim Index As Integer = -1I
        If Me.Items.Count > 0I Then
            For Counter As Integer = 0I To Me.Items.Count - 1I
                If Me.GetItemRectangle(Counter).Contains(X, Y) Then Index = Counter
            Next Counter
        End If
        Return Index
    End Function

#End Region
#Region " Properties: DrawMode, DrawGrid, DividerColor, DividerThickness "

    <Browsable(False), EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never), _
    DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DefaultValue("OwnerDrawVariable")> _
    Public Shadows ReadOnly Property DrawMode() As Windows.Forms.DrawMode
        Get
            Return Windows.Forms.DrawMode.OwnerDrawVariable
        End Get
    End Property

    <DefaultValue(True), Description("Draw a divider line under each item."), Category("ItemsDivider")> _
    Public Property DrawDivider() As Boolean
        Get
            Return m_DrawDivider
        End Get
        Set(ByVal value As Boolean)
            If m_DrawDivider <> value Then
                m_DrawDivider = value
                Me.Invalidate()
            End If
        End Set
    End Property

    <DefaultValue("WindowText"), Description("Color of the divider line between the items."), Category("ItemsDivider")> _
    Public Property DividerColor() As Color
        Get
            Return m_DividerColor
        End Get
        Set(ByVal value As Color)
            If Not m_DividerColor.Equals(value) Then
                m_DividerColor = value
                If m_DividerPen IsNot Nothing Then m_DividerPen.Dispose()
                m_DividerPen = New Pen(m_DividerColor, m_DividerThickness)
                Me.Invalidate()
            End If
        End Set
    End Property

    <DefaultValue(1.0!), Description("Thinkness of the divider line between the items."), Category("ItemsDivider")> _
    Public Property DividerThickness() As Single
        Get
            Return m_DividerThickness
        End Get
        Set(ByVal value As Single)
            If m_DividerThickness <> value Then
                m_DividerThickness = value
                If m_DividerPen IsNot Nothing Then m_DividerPen.Dispose()
                m_DividerPen = New Pen(m_DividerColor, m_DividerThickness)
                Me.Invalidate()
            End If
        End Set
    End Property

    <DefaultValue(True), Description("Enables items to be varying heights depending on string line breaks.")> _
    Public Property MultilineEnabled() As Boolean
        Get
            Return m_MultilineEnabled
        End Get
        Set(ByVal value As Boolean)
            If m_MultilineEnabled <> value Then
                m_MultilineEnabled = value
                Me.Invalidate()
                Call OnMultilineEnabledChanged(EventArgs.Empty)
            End If
        End Set
    End Property

#End Region

    Protected Sub OnMultilineEnabledChanged(ByVal e As EventArgs)
        RaiseEvent MultilineEnabledChanged(Me, e)
    End Sub

End Class
