To change the Theme border color of the DropDown List of a ComboBox, you need to handle the WM_NCPAINT message of the List Control, which is sent to the handle of the Window when the non-client area needs to be painted: usually, when the DropDown is shown.
To get the handle of the List Control of a ComboBox, you can use the GetComboBoxInfo() function: the handle of its List Control and Edit Control are returned in a COMBOBOXINFO structure.
You can then assign the List Control handle to a NativeWindow, so you can override its WndProc and trap WM_NCPAINT.
When the message is received, get the handle to the Device Context (HDC) of the List Control, using the GetWindowDc() function and pass it to the Graphics.FromHdc() method, to create a Graphics object that can be used to draw on this surface.
▶ Reading the documentation about the WM_NCPAINT message, you may notice that WPARAM should reference the update Region Handle: but it's usually IntPtr.Zero, that's why we need GetWindowDc().
Release the handle to the Device Context calling ReleaseDC() after (important).
That's pretty much it.
The custom ComboBox Control exposes a public ListBorderColor property that is used to set the Color of the List Control border at Design-Time and Run-Time.
Imports System.ComponentModel
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
<DesignerCategory("code")>
Public Class ComboBoxExt
Inherits ComboBox
Private listControl As ListNativeWindow = Nothing
Private m_ListBorderColor As Color = Color.Transparent
Public Sub New()
End Sub
<DefaultValue(GetType(Color), "Transparent")>
Public Property ListBorderColor As Color
Get
Return m_ListBorderColor
End Get
Set
m_ListBorderColor = Value
If listControl IsNot Nothing Then
listControl.BorderColor = m_ListBorderColor
End If
End Set
End Property
Protected Overrides Sub OnHandleCreated(e As EventArgs)
MyBase.OnHandleCreated(e)
listControl = New ListNativeWindow(GetComboBoxListInternal(Me.Handle))
listControl.BorderColor = ListBorderColor
End Sub
Protected Overrides Sub OnHandleDestroyed(e As EventArgs)
listControl.ReleaseHandle()
MyBase.OnHandleDestroyed(e)
End Sub
Public Class ListNativeWindow
Inherits NativeWindow
Public Sub New()
Me.New(IntPtr.Zero)
End Sub
Public Sub New(hWnd As IntPtr)
If hWnd <> IntPtr.Zero Then AssignHandle(hWnd)
End Sub
Public Property BorderColor As Color = Color.Transparent
Protected Overrides Sub WndProc(ByRef m As Message)
MyBase.WndProc(m)
Select Case m.Msg
Case WM_NCPAINT
Dim hDC As IntPtr = GetWindowDC(Me.Handle)
Try
Using g = Graphics.FromHdc(hDC),
pen = New Pen(BorderColor)
Dim rect = g.VisibleClipBounds
g.DrawRectangle(pen, 0, 0, rect.Width - 1, rect.Height - 1)
End Using
Finally
ReleaseDC(Me.Handle, hDC)
End Try
m.Result = IntPtr.Zero
End Select
End Sub
End Class
<DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Unicode)>
Friend Shared Function GetComboBoxInfo(hWnd As IntPtr, ByRef pcbi As COMBOBOXINFO) As Boolean
End Function
<DllImport("user32.dll", SetLastError:=True)>
Friend Shared Function GetWindowDC(hWnd As IntPtr) As IntPtr
End Function
<DllImport("user32.dll", SetLastError:=True)>
Friend Shared Function ReleaseDC(hWnd As IntPtr, hDc As IntPtr) As Boolean
End Function
Friend Const WM_NCPAINT As Integer = &H85
<StructLayout(LayoutKind.Sequential)>
Friend Structure COMBOBOXINFO
Public cbSize As Integer
Public rcItem As Rectangle
Public rcButton As Rectangle
Public buttonState As Integer
Public hwndCombo As IntPtr
Public hwndEdit As IntPtr
Public hwndList As IntPtr
Public Sub Init()
cbSize = Marshal.SizeOf(Of COMBOBOXINFO)()
End Sub
End Structure
Friend Function GetComboBoxListInternal(cboHandle As IntPtr) As IntPtr
Dim cbInfo = New COMBOBOXINFO()
cbInfo.Init()
GetComboBoxInfo(cboHandle, cbInfo)
Return cbInfo.hwndList
End Function
End Class