# Embedded Controls

Better ListView: Ultimate .NET ListView replacement control for WinForms (C#, VB.NET)
Download More Info

Embedded editing controls can be thought of as an extension to label edit functionality (see Label Editing for more information).

Every item and sub-item has a cell area on which an editing control can be placed. In the simplest case (LabelEdit set to true), the editing control is basically a System.Windows.Forms.TextBox control. It is actually an instance of BetterListViewTextBoxEmbeddedControl, which is a TextBox wrapper implementing IBetterListViewEmbeddedControl interface. Any control can be used as embedded control in Bettter ListView if implements one of these interfaces:

- IBetterListViewEmbeddedControl

- IBetterListViewEmbeddedControlExtended

The custom embedded control is shown on the image below. When user clicks on sub-item text (an abbreviation of tea grading), an editing control appears on the top-left corner of the cell area. The control contains buttons for accepting and cancelling changes:

## Implementing IBetterListViewEmbeddedControl

This interface contains prescription for minimum amount of functionality required by an embedded control:

- Get label text for currently edited data.

- Data accepting and cancelling events (e.g. if the control has an OK button).

- Set control size given the cell area and positioning data.

- Move data from sub-item to the control.

- Move data from the control to the sub-item.

Let's make a sample control. We will make a TextBox-based embedded control for editing words in lower-case. First, we inherit TextBox and implement IBetterListViewEmbeddedControl interface:

C#

    /// <summary>
    ///   Represents a custom control embeddable in Better ListView.
    /// </summary>
    public class TextBoxEmbeddedControl : TextBox, IBetterListViewEmbeddedControl

Visual Basic

    ''' <summary>
    '''   Represents a custom control embeddable in Better ListView.
    ''' </summary>
    Public Class TextBoxEmbeddedControl
        Inherits TextBox
        Implements IBetterListViewEmbeddedControl

Then we implement the LabelText property:

C#

    /// <summary>
    ///   current (edited) label text
    /// </summary>
    public string LabelText
    {
        get
        {
            return Text.ToLower();
        }
    }

Visual Basic

    ''' <summary>
    '''   current (edited) label text
    ''' </summary>
    Public ReadOnly Property LabelText() As String
        Get
            Return Text.ToLower()
        End Get
    End Property

As you can see, the text of the TextBox is converted to lower case since we want item/sub-item labels to be only in lower case.

Next, we implement RequestAccept and RequestCancel events:

C#

    /// <summary>
    ///   request accepting updated data in BetterListView
    /// </summary>
    public event EventHandler RequestAccept;

    /// <summary>
    ///   request cancelling editing
    /// </summary>
    public event EventHandler RequestCancel;

Visual Basic

    ''' <summary>
    '''   request accepting updated data in BetterListView
    ''' </summary>
    Public Event RequestAccept As EventHandler Implements IBetterListViewEmbeddedControl.RequestAccept

    ''' <summary>
    '''   request cancelling editing
    ''' </summary>
    Public Event RequestCancel As EventHandler Implements IBetterListViewEmbeddedControl.RequestCancel

Next, we implement GetData and SetData methods:

C#

    /// <summary>
    ///   get data from the specified sub-item in control
    /// </summary>
    /// <param name = "subItem">sub-item whose data are being edited</param>
    public void GetData(BetterListViewSubItem subItem)
    {
    	Text = subItem.Text;
    }

    /// <summary>
    ///   set data from control to the specified sub-item
    /// </summary>
    /// <param name = "subItem">sub-item whose data are being edited</param>
    public void SetData(BetterListViewSubItem subItem)
    {
        subItem.Text = LabelText;
    }

Visual Basic

    ''' <summary>
    '''   get data from the specified sub-item in control
    ''' </summary>
    ''' <param name = "subItem">sub-item whose data are being edited</param>
    Public Sub GetData(ByVal subItem As BetterListViewSubItem) Implements IBetterListViewEmbeddedControl.GetData

        Text = subItem.Text

    End Sub

    ''' <summary>
    '''   set data from control to the specified sub-item
    ''' </summary>
    ''' <param name = "subItem">sub-item whose data are being edited</param>
    Public Sub SetData(ByVal subItem As BetterListViewSubItem) Implements IBetterListViewEmbeddedControl.SetData

        subItem.Text = LabelText

    End Sub

These method are trivial since we need not to do any data conversions (the only conversion here is lowering the case of edited text in the LabelText getter).

The last method contained in the interface is SetSize method, which needs not to be implemented (the body can be kept empty). You implement this method only if you need to adjust control's size when label edit starts.

The constructor should be implemented like this:

C#

    /// <summary>
    ///   Initializes a new instance of the <see cref = "TextBoxEmbeddedControl" /> class.
    /// </summary>
    public TextBoxEmbeddedControl()
    {
        AcceptsReturn = true;
        CausesValidation = false;
    }

Visual Basic

    ''' <summary>
    '''   Initializes a new instance of the <see cref = "TextBoxEmbeddedControl" /> class.
    ''' </summary>
    Public Sub New()

        AcceptsReturn = True
        CausesValidation = False

    End Sub

The AcceptsReturn property is set to true because we will handle the ENTER key (and raise RequestAccept event appropriately).

The CausesValidation property is set to false because it is a good practice in this situation.

Both input and output data are validated in the IBetterListViewEmbeddedControl implementation and validation of some third-party controls can prevent whole form with the control from closing.

The last thing we implement is handling of the ENTER key for accepting the data and the ESCAPE key for cancelling:

C#

    protected override void OnKeyDown(KeyEventArgs e)
    {
        if (e.KeyCode == Keys.Enter &&
            RequestAccept != null)
        {
            RequestAccept(this, EventArgs.Empty);

            e.Handled = true;

            return;
        }

        if (e.KeyCode == Keys.Escape &&
            RequestCancel != null)
        {
            RequestCancel(this, EventArgs.Empty);

            e.Handled = true;

            return;
        }

        base.OnKeyDown(e);
    }

Visual Basic

    Protected Overrides Sub OnKeyDown(e As KeyEventArgs)

        If e.KeyCode = Keys.Enter AndAlso RequestAccept IsNot Nothing Then

            RequestAccept(Me, EventArgs.Empty)

            e.Handled = True

            Return

        End If

        If e.KeyCode = Keys.Escape AndAlso RequestCancel IsNot Nothing Then

        RequestCancel(Me, EventArgs.Empty)

        e.Handled = True

        Return

        End If

        MyBase.OnKeyDown(e)

    End Sub

It is a common good practice to implement interfaces explicitly. The sample implementation is implicit for the sake of better readability. Embedded controls implemented in BetterListView.dll are implemented implicitly (and marked virtual) to allow for being inherited (e.g. MyCustomControl : BetterListViewEmbeddedControl) and you may possibly want to override any part of the interface implementation.

## Implementing IBetterListViewEmbeddedControlExtended

The extended interface has currently only one method called RequestEndEdit. This method can be called by the Better ListView, when it asks the control whether it is ready to end editing. The control can return a boolean value (true - continue EndEdit, false - refuse to end editing). There are many situations when the label editing is terminated (e.g. scrolling the control, selecting items...) and terminating the label edit is not always wanted (this is a case of System.Windows.Forms.DateTimePicker control, which sometimes behaves as being transparent for mouse clicks and thus being closed because of click-through on the Better ListView client area - the RequestEndEdit method fixes such possible behavior of third party controls).

## Sample Source Code

Form with Better ListView containing some columns and items:

C#

    /// <summary>
    ///   Shows embedding of custom controls into Better ListView.
    /// </summary>
    internal sealed partial class EmbeddedControlSampleForm : Form
    {
        /// <summary>
        ///   Initializes a new instance of the <see cref = "EmbeddedControlSampleForm" /> class.
        /// </summary>
        public EmbeddedControlSampleForm()
        {
            InitializeComponent();

            this.listView.BeginUpdate();

            this.listView.Columns.AddRange(new[]
                                           {
                                               new BetterListViewColumnHeader("Document name", 160),
                                               new BetterListViewColumnHeader("Access", 128)
                                           });

            this.listView.Items.AddRange(
                new[]
                {
                    new BetterListViewItem(new[] { "hydro-report.pdf", "read" }),
                    new BetterListViewItem(new[] { "magnetic_resonance.docx", "read write" }),
                    new BetterListViewItem(new[] { "billing forms (2011).zip", "read" })
                });

            this.listView.LabelEditActivation = (BetterListViewLabelEditActivation.Keyboard | BetterListViewLabelEditActivation.SingleClick);
            this.listView.LabelEditModeSubItems = BetterListViewLabelEditMode.CustomControl;

            this.listView.EndUpdate();

            this.listView.RequestEmbeddedControl += ListViewRequestEmbeddedControl;
        }

        private IBetterListViewEmbeddedControl ListViewRequestEmbeddedControl(object sender, BetterListViewRequestEmbeddedControlEventArgs eventArgs)
        {
            if (eventArgs.SubItem.Index == 1)
            {
                return (new DocumentAccessConrol());
            }

            return null;
        }
    }

Visual Basic

    ''' <summary>
    '''   Shows embedding of custom controls into Better ListView.
    ''' </summary>
    Partial Friend NotInheritable Class EmbeddedControlSampleForm

        ''' <summary>
        '''   Initializes a new instance of the <see cref = "EmbeddedControlSampleForm" /> class.
        ''' </summary>
        Public Sub New()

            InitializeComponent()

            ListView.BeginUpdate()

            ListView.Columns.AddRange(
                New BetterListViewColumnHeader() { _
                                                     New BetterListViewColumnHeader("Document name", 160),
                                                     New BetterListViewColumnHeader("Access", 128)
                                                 })

            ListView.Items.AddRange(
                New BetterListViewItem() { _
                                             New BetterListViewItem(New String() {"hydro-report.pdf", "read"}),
                                             New BetterListViewItem(New String() {"magnetic_resonance.docx", "read write"}),
                                             New BetterListViewItem(New String() {"billing forms (2011).zip", "read"})
                                         })

            ListView.LabelEditActivation =
                (BetterListViewLabelEditActivation.Keyboard Or BetterListViewLabelEditActivation.SingleClick)
            ListView.LabelEditModeSubItems = BetterListViewLabelEditMode.CustomControl

            ListView.EndUpdate()

            AddHandler ListView.RequestEmbeddedControl, AddressOf ListViewRequestEmbeddedControl

        End Sub

        Private Function ListViewRequestEmbeddedControl(ByVal sender As Object,
                                                         ByVal eventArgs As BetterListViewRequestEmbeddedControlEventArgs) _
            As IBetterListViewEmbeddedControl

            If eventArgs.SubItem.Index = 1 Then
                Return (New DocumentAccessConrol())
            End If

            Return Nothing

        End Function

    End Class

DocumentAccessControl class used as complex embedded control (see EmbeddedControlSampleForm sample in the provided C# and Visual Basic samples for full source code):

C#

    /// <summary>
    ///   Represents a custom control embeddable in Better ListView.
    /// </summary>
    [ToolboxItem(false)]
    internal sealed partial class DocumentAccessConrol : UserControl, IBetterListViewEmbeddedControl
    {
        private const string StringRead = "read";
        private const string StringWrite = "write";

        /// <summary>
        ///   current (edited) label text
        /// </summary>
        public string LabelText
        {
            get
            {
                // convert control's state to label
                if (this.checkBoxRead.Checked &&
                    this.checkBoxWrite.Checked)
                {
                    return String.Format("{0} {1}", StringRead, StringWrite);
                }

                if (this.checkBoxRead.Checked)
                {
                    return StringRead;
                }

                if (this.checkBoxWrite.Checked)
                {
                    return StringWrite;
                }

                return String.Empty;
            }
        }

        /// <summary>
        ///   request accepting updated data in BetterListView
        /// </summary>
        public event EventHandler RequestAccept;

        /// <summary>
        ///   request cancelling editing
        /// </summary>
        public event EventHandler RequestCancel;

        /// <summary>
        ///   Initializes a new instance of the <see cref = "DocumentAccessConrol" /> class.
        /// </summary>
        public DocumentAccessConrol()
        {
            InitializeComponent();

            //NOTE: disabling validation prevents form close cancellation
            CausesValidation = false;

            foreach (Control control in Controls)
            {
                control.LostFocus += ControlOnLostFocus;
            }
        }

        /// <summary>
        ///   get data from the specified sub-item in control
        /// </summary>
        /// <param name = "subItem">sub-item whose data are being edited</param>
        public void GetData(BetterListViewSubItem subItem)
        {
            // convert label to control's state
            this.checkBoxRead.Checked = subItem.Text.Contains(StringRead);
            this.checkBoxWrite.Checked = subItem.Text.Contains(StringWrite);
        }

        /// <summary>
        ///   set data from control to the specified sub-item
        /// </summary>
        /// <param name = "subItem">sub-item whose data are being edited</param>
        public void SetData(BetterListViewSubItem subItem)
        {
            subItem.Text = LabelText;
        }

        /// <summary>
        ///   set control size
        /// </summary>
        /// <param name = "subItem">sub-item whose data are being edited</param>
        /// <param name = "placement">placement of the embedded control within sub-item</param>
        public void SetSize(BetterListViewSubItem subItem, BetterListViewEmbeddedControlPlacement placement)
        {
            // keep size of the control unchanged
        }

        private void ControlOnLostFocus(object sender, EventArgs eventArgs)
        {
            //
            // NOTE: this code is needed just for hiding embedded control with sub-controls when user changes active form while label editing
            //
            bool anyFocused = Focused;

            if (anyFocused == false)
            {
                foreach (Control control in Controls)
                {
                    if (control.Focused)
                    {
                        anyFocused = true;

                        break;
                    }
                }
            }

            if (anyFocused == false)
            {
                RequestAccept(this, eventArgs);
            }
        }

        private void ButtonOKClick(object sender, EventArgs e)
        {
            RequestAccept(this, e);
        }

        private void ButtonCancelClick(object sender, EventArgs e)
        {
            RequestCancel(this, e);
        }
    }

Visual Basic

    ''' <summary>
    '''   Represents a custom control embeddable in Better ListView.
    ''' </summary>
    <ToolboxItem(False)>
    Partial Friend NotInheritable Class DocumentAccessConrol
        Inherits UserControl
        Implements IBetterListViewEmbeddedControl

        Private Const StringRead As String = "read"
        Private Const StringWrite As String = "write"

        ''' <summary>
        '''   current (edited) label text
        ''' </summary>
        Public ReadOnly Property LabelText() As String Implements IBetterListViewEmbeddedControl.LabelText
            Get
                ' convert control's state to label
                If CheckBoxRead.Checked AndAlso CheckBoxWrite.Checked Then
                    Return [String].Format("{0} {1}", StringRead, StringWrite)
                End If

                If CheckBoxRead.Checked Then
                    Return StringRead
                End If

                If CheckBoxWrite.Checked Then
                    Return StringWrite
                End If

                Return [String].Empty
            End Get
        End Property

        ''' <summary>
        '''   request accepting updated data in BetterListView
        ''' </summary>
        Public Event RequestAccept As EventHandler Implements IBetterListViewEmbeddedControl.RequestAccept

        ''' <summary>
        '''   request cancelling editing
        ''' </summary>
        Public Event RequestCancel As EventHandler Implements IBetterListViewEmbeddedControl.RequestCancel

        ''' <summary>
        '''   Initializes a new instance of the <see cref = "DocumentAccessConrol" /> class.
        ''' </summary>
        Public Sub New()

            InitializeComponent()

            'NOTE: disabling validation prevents form close cancellation
            CausesValidation = False

            For Each control As Control In Controls
                AddHandler control.LostFocus, AddressOf ControlOnLostFocus
            Next

        End Sub

        ''' <summary>
        '''   get data from the specified sub-item in control
        ''' </summary>
        ''' <param name = "subItem">sub-item whose data are being edited</param>
        Public Sub GetData(ByVal subItem As BetterListViewSubItem) Implements IBetterListViewEmbeddedControl.GetData

            ' convert label to control's state
            CheckBoxRead.Checked = subItem.Text.Contains(StringRead)
            CheckBoxWrite.Checked = subItem.Text.Contains(StringWrite)

        End Sub

        ''' <summary>
        '''   set data from control to the specified sub-item
        ''' </summary>
        ''' <param name = "subItem">sub-item whose data are being edited</param>
        Public Sub SetData(ByVal subItem As BetterListViewSubItem) Implements IBetterListViewEmbeddedControl.SetData

            subItem.Text = LabelText

        End Sub

        ''' <summary>
        '''   set control size
        ''' </summary>
        ''' <param name = "subItem">sub-item whose data are being edited</param>
        ''' <param name = "placement">placement of the embedded control within sub-item</param>
        Public Sub SetSize(ByVal subItem As BetterListViewSubItem,
                            ByVal placement As BetterListViewEmbeddedControlPlacement) _
            Implements IBetterListViewEmbeddedControl.SetSize

            ' keep size of the control unchanged

        End Sub

        Private Sub ControlOnLostFocus(ByVal sender As Object, ByVal eventArgs As EventArgs)

            '
            ' NOTE: this code is needed just for hiding embedded control with sub-controls when user changes active form while label editing
            '
            Dim anyFocused As Boolean = Focused

            If anyFocused = False Then
                For Each control As Control In Controls
                    If control.Focused Then
                        anyFocused = True

                        Exit For
                    End If
                Next
            End If

            If anyFocused = False Then
                RaiseEvent RequestAccept(Me, eventArgs)
            End If

        End Sub

        Private Sub ButtonOKClick(ByVal sender As Object, ByVal e As EventArgs) Handles ButtonOK.Click
            RaiseEvent RequestAccept(Me, e)
        End Sub

        Private Sub ButtonCancelClick(ByVal sender As Object, ByVal e As EventArgs) Handles ButtonCancel.Click
            RaiseEvent RequestCancel(Me, e)
        End Sub

    End Class

Better ListView: Ultimate .NET ListView replacement control for WinForms (C#, VB.NET)
Download More Info