How do I create command link buttons (with multiple lines of text) in VB.NET?

.net, button, vb.net, winapi, winforms

Solution

The button shown in the screenshot is actually one used throughout the Aero UI. It's a custom style of button called a "command link", and it can be easily applied to a standard `Button` control.

Unfortunately, the WinForms libraries don't expose this functionality via a simple property, but that's easily fixable with a bit of P/Invoke.

The style you're looking for is called `BS_COMMANDLINK`. According to the documentation, this style:

Creates a command link button that behaves like a `BS_PUSHBUTTON` style button, but the command link button has a green arrow on the left pointing to the button text. A caption for the button text can be set by sending the `BCM_SETNOTE` message to the button.

Here's a little custom button control class that extends the standard WinForms `Button` control, and implements the "command link" style as a property you can configure in the designer or through code.

A couple of things to note about the code:

The `FlatStyle` property must always be set to `FlatStyle.System`, which forces the use of the standard Windows API Button control, rather than one drawn by WinForms code. This is required for the `BS_COMMANDLINK` style to work (because it's only supported by the native controls), and it produces a better looking button control (with throbbing effects, etc.) anyway. To force this, I've overridden the `FlatStyle` property and set a default value.

The `CommandLink` property is how you toggle on and off the "command link" style. It's off by default, giving you a standard button control, so you can replace all of the button controls in your application with this one, if you want, just for convenience. When you turn the property on (set it to `True`), then you get a fancy, multiline command link button.

The command link button's caption is the same caption as is displayed on a standard button. However, caption button also support a "description" on the second line. This is configurable through another property, called `CommandLinkNote`, after the WinAPI message, `BCM_SETNOTE`. When you have the button configured as a standard button control (`CommandLink = False`), the value of this property is ignored.

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

Public Class ButtonEx : Inherits Button

    Private _commandLink As Boolean
    Private _commandLinkNote As String

    Public Sub New() : MyBase.New()
        'Set default property values on the base class to avoid the Obsolete warning
        MyBase.FlatStyle = FlatStyle.System
    End Sub

    <Category("Appearance")> _
    <DefaultValue(False)> _
    <Description("Specifies this button should use the command link style. " & _
                 "(Only applies under Windows Vista and later.)")> _
    Public Property CommandLink As Boolean
        Get
            Return _commandLink
        End Get
        Set(ByVal value As Boolean)
            If _commandLink <> value Then
                _commandLink = value
                Me.UpdateCommandLink()
            End If
        End Set
    End Property

    <Category("Appearance")> _
    <DefaultValue("")> _
    <Description("Sets the description text for a command link button. " & _
                 "(Only applies under Windows Vista and later.)")> _
    Public Property CommandLinkNote As String
        Get
            Return _commandLinkNote
        End Get
        Set(value As String)
            If _commandLinkNote <> value Then
                _commandLinkNote = value
                Me.UpdateCommandLink()
            End If
        End Set
    End Property

    <Browsable(False)> <EditorBrowsable(EditorBrowsableState.Never)> _
    <DebuggerBrowsable(DebuggerBrowsableState.Never)> _
    <Obsolete("This property is not supported on the ButtonEx control.")> _
    <DefaultValue(GetType(FlatStyle), "System")> _
    Public Shadows Property FlatStyle As FlatStyle
        'Set the default flat style to "System", and hide this property because
        'none of the custom properties will work without it set to "System"
        Get
            Return MyBase.FlatStyle
        End Get
        Set(ByVal value As FlatStyle)
            MyBase.FlatStyle = value
        End Set
    End Property

#Region "P/Invoke Stuff"
    Private Const BS_COMMANDLINK As Integer = &HE
    Private Const BCM_SETNOTE As Integer = &H1609

    <DllImport("user32.dll", CharSet:=CharSet.Unicode, SetLastError:=False)> _
    Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal wParam As IntPtr, _
                                        <MarshalAs(UnmanagedType.LPWStr)> ByVal lParam As String) As IntPtr
    End Function

    Private Sub UpdateCommandLink()
        Me.RecreateHandle()
        SendMessage(Me.Handle, BCM_SETNOTE, IntPtr.Zero, _commandLinkNote)
    End Sub

    Protected Overrides ReadOnly Property CreateParams As CreateParams
        Get
            Dim cp As CreateParams = MyBase.CreateParams

            If Me.CommandLink Then
                cp.Style = cp.Style Or BS_COMMANDLINK
            End If

            Return cp
        End Get
    End Property
#End Region

End Class

Problem

I know how to use the default buttons item, but is there any way to achieve the style of multiline buttons (or maybe rather, "clickable text"?) like shown below? The situation is that I have an interface for the user to select what kind of file he wishes to establish, and there has to be a brief description under the larger, main line of text. I'm only planning to run this on Windows 7, so I don't need to worry about backwards compatibility with older versions of Windows

Original source