15
votes

I need to create a combo-box that

  • provides a resizable file name completion list and
  • keeps a history of previous inputs and shows them in a drop-down list

similar to the "Run" dialog in Windows.

Resizable Completion List:

file name completion

Drop-down List:

drop-down list

Are there any suitable controls ready in WinForms, WPF, or in any open-source libraries? Or I need to implement it manually using low-level controls?

Thank you in advance!

3
Completion list is resizable in Windows 8 only.Vladimir

3 Answers

8
votes

Solution for WPF

Part 1

Principle, you can use styles and templates for the implementation of your question. In the ComboBox the result is given in the Popup, but default it does not support changing the size. Add resizing is not difficult, if you use event DragDelta. Example:

private void MyThumb_DragDelta(object sender, DragDeltaEventArgs e)
{           
    double yadjust = MyPopup.Height + e.VerticalChange;
    double xadjust = MyPopup.Width + e.HorizontalChange;

    if ((xadjust >= 0) && (yadjust >= 0))
    {
        MyPopup.Width = xadjust;
        MyPopup.Height = yadjust;
    }
} 

The event is better to set on the Thumb control (He also has events DragStarted, DragCompleted).

It's all very well, but we do need to do inside the ComboBox. One way is to use the Style and Template. To begin, add a Thumb in style ComboBox so it appears in the expanded list:

...

<Setter Property="Template">
    <Setter.Value>
        <ControlTemplate TargetType="ComboBox">
            <Grid Name="MainGrid">
                <ToggleButton Name="ToggleButton" Template="{StaticResource ComboBoxToggleButton}" Grid.Column="2" Focusable="False" IsChecked="{Binding Path=IsDropDownOpen,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}" ClickMode="Press" />

                <ContentPresenter Name="ContentSite" IsHitTestVisible="False" Content="{TemplateBinding SelectionBoxItem}" ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}" ContentTemplateSelector="{TemplateBinding ItemTemplateSelector}" Margin="3,3,23,3" VerticalAlignment="Center" HorizontalAlignment="Left" />

                <TextBox x:Name="PART_EditableTextBox" Style="{x:Null}" Template="{StaticResource ComboBoxTextBox}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="3,3,23,3" Focusable="True" Background="{TemplateBinding Background}" Visibility="Hidden" IsReadOnly="{TemplateBinding IsReadOnly}" />

                <!-- Expanded list store here -->
                <Popup Name="Popup" Placement="Bottom" IsOpen="{TemplateBinding IsDropDownOpen}" AllowsTransparency="True" Focusable="False" PopupAnimation="Slide">
                    <Grid Name="DropDown" Width="100" Height="100" SnapsToDevicePixels="True">
                    <Border x:Name="DropDownBorder" Background="White" BorderThickness="1" BorderBrush="Gray" />

                    <ScrollViewer Margin="2" SnapsToDevicePixels="True">
                        <StackPanel IsItemsHost="True" KeyboardNavigation.DirectionalNavigation="Contained" />
                    </ScrollViewer>

                    <!-- Our Thumb -->
                    <Thumb x:Name="ResizeGripThumb" Style="{StaticResource ResizeGripStyle}" HorizontalAlignment="Right" Margin="0,0,2,2" Background="Transparent" VerticalAlignment="Bottom" Width="12" Height="12" />
                </Grid>
            </Popup>
        </Grid>
...             

For normal display Thumb, add to style it with a Path:

<!-- ResizeGrip Style -->
<Style x:Key="ResizeGripStyle" TargetType="{x:Type Thumb}">
    <Setter Property="SnapsToDevicePixels" Value="True" />
    <Setter Property="Cursor" Value="SizeNWSE" />

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
                <Grid>
                    <Path Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Stretch="Fill" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" Fill="Gray" Data="M8,0L10,0 10,2 8,2z M4,4L6,4 6,6 4,6z M8,4L10,4 10,6 8,6z M0,8L2,8 2,10 0,10z M4,8L6,8 6,10 4,10z M8,8L10,8 10,10 8,10z "/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now, at this stage, we have shown ResizeGrip in expanded list. But the default ScrollBar, then closes it with his presence, so it also define the style for ScrollBar. It will change the margin VerticalThumb, thus:

...

<!-- VerticalThumb for ScollBar -->
<Style x:Key="VerticalThumb" TargetType="{x:Type Thumb}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
                <Rectangle Fill="Gray" Margin="-1,-1,-3,16" />
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>        

Now, there is a normal display of the main components. Declared ComboBox in XAML:

<ComboBox Name="ResizeComboBox" Style="{StaticResource MyComboBox}" IsEditable="True" IsTextSearchEnabled="True" FontSize="14" SelectedIndex="0" Width="100" Height="30">
    <ComboBoxItem>1</ComboBoxItem>
    <ComboBoxItem>2</ComboBoxItem>
    <ComboBoxItem>3</ComboBoxItem>
    <ComboBoxItem>4</ComboBoxItem>
    <ComboBoxItem>5</ComboBoxItem>
    <ComboBoxItem>6</ComboBoxItem>
    <ComboBoxItem>7</ComboBoxItem>
    <ComboBoxItem>8</ComboBoxItem>
</ComboBox>

It remains to set a handler for resize the Popup. I'll make it search-control in the template by using the function FindChild<T>. To be safe, I'll do it in the event ContentRendered of Window, to know that all the elements loaded:

private void Window_ContentRendered(object sender, EventArgs e)
{
    // Find MainGrid in our ComboBox template
    Grid MyMainGrid = FindChild<Grid>(ResizeComboBox, "MainGrid");

    // Find Popup in Grid
    Popup MyPopup = MyMainGrid.FindName("Popup") as Popup;

    // Find Thumb in Popup
    Thumb MyThumb = MyPopup.FindName("ResizeGripThumb") as Thumb;

    // Set the handler
    MyThumb.DragDelta += new DragDeltaEventHandler(MyThumb_DragDelta);
}

Listing of FindChild<>:

    public static T FindChild<T>(DependencyObject parent, string childName) where T : DependencyObject
    {
        if (parent == null)
        {
            return null;
        }

        T foundChild = null;

        int childrenCount = VisualTreeHelper.GetChildrenCount(parent);

        for (int i = 0; i < childrenCount; i++)
        {
            var child = VisualTreeHelper.GetChild(parent, i);
            T childType = child as T;

            if (childType == null)
            {
                foundChild = FindChild<T>(child, childName);

                if (foundChild != null) break;
            }
            else
                if (!string.IsNullOrEmpty(childName))
                {
                    var frameworkElement = child as FrameworkElement;

                    if (frameworkElement != null && frameworkElement.Name == childName)
                    {
                        foundChild = (T)child;
                        break;
                    }
                    else
                    {
                        foundChild = FindChild<T>(child, childName);

                        if (foundChild != null)
                        {
                            break;
                        }
                    }
                }
                else
                {
                    foundChild = (T)child;
                    break;
                }
        }

        return foundChild;
    }

Listing of handler MyThumb_DragDelta:

private void MyThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
    Thumb MyThumb = sender as Thumb;
    Grid MyGrid = MyThumb.Parent as Grid;

    // Set the new Width and Height fo Grid, Popup they will inherit
    double yAdjust = MyGrid.Height + e.VerticalChange;
    double xAdjust = MyGrid.Width + e.HorizontalChange;

    // Set new Height and Width
    if ((xAdjust >= 0) && (yAdjust >= 0))
    {
        MyGrid.Width = xAdjust;
        MyGrid.Height = yAdjust;
    }
}       

It so:

enter image description here

Some notes: To set the template values, they should have a default value, or the value will be NaN, and we can not set them. We have these parameters are set here:

<Grid Name="DropDown" Width="100" Height="100" SnapsToDevicePixels="True">      

A complete listing of templates and code can be found here, since they are large in volume. Styles can not be easily changed, because they were made in a hurry, so they should do for themselves.

Part 2

As for storing the entered data, it depends on your goals. I think you can do something like this:

  1. Create a list (may be ObservableCollection) to store the items.
  2. After successfully entering element, for example - which he was found in some sources, save it to your list.
  3. Binding this list in ComboBox.

Just set the properties IsEditable= "True" and IsTextSearchEnabled= "True" to display the input character in the drop-down list (as in my example).

Thus, you will have a list in which the elements are added, which can be shown to the user.

1
votes

For WindowsForms you need to specify combos properties AutoCompleteSource = AutoCompleteSource.FileSystem and (optionally) AutoCompleteMode = AutoCompleteMode.Suggest. Its "provides a resizable file name completion list".

I do not know a embedded solution for a "keep a history of previous inputs and shows them in a drop-down list".

1
votes

Here is a piece of reusable code that may get you started, here is how you use it with a standard Windows Forms application that has a standard Textbox on it:

public partial class Form1 : Form
{
    private AutoCompletion _ac;

    public Form1()
    {
        InitializeComponent();

        // add the autocompletion tool to the 'textBox1' text box 
        _ac = new AutoCompletion(textBox1);
        _ac.TextChanged += AutoCompletionTextChanged;
    }

    private void AutoCompletionTextChanged(object sender, EventArgs e)
    {
        if (ShowSomething())
        {
            // clear the items and add 50 as an example
            _ac.Items.Clear();
            for (int i = 0; i < 50; i++)
            {
                AutoCompletion.AutoCompletionItem item = new AutoCompletion.AutoCompletionItem();
                item.Text = "Item " + i;
                _ac.Items.Add(item);
            }
            _ac.SelectedItem = _ac.Items[0]; // pre-select first one as an example
            _ac.Show(); // show the autocompletion window
            return;
        }
    }

    private bool ShowSomething()
    {
        return true; // TODO: implement this
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && (components != null))
        {
            components.Dispose();
        }

        base.Dispose(disposing);

        // don't forget to dispose the tool
        if (_ac != null)
        {
            _ac.Dispose();
        }
    }
}

And here is the code:

public sealed class AutoCompletion: IDisposable
{
    private AutoCompleteForm _form;
    private AutoCompletionItem _selectedItem;
    private readonly List<AutoCompletionItem> _items = new List<AutoCompletionItem>();

    public event EventHandler TextChanged;

    public AutoCompletion(TextBoxBase textBox)
    {
        if (textBox == null)
            throw new ArgumentNullException("textBox");

        _form = new AutoCompleteForm(this);
        _form.SetOwner(textBox);
        Margin = new Padding(2, 0, 30, 0);
        ImageMargin = 2;
        BorderStyle = FormBorderStyle.SizableToolWindow;
        MinimumLines = 1;
        MaximumLines = 100;
        IsEnabled = true;
    }

    public bool IsEnabled
    {
        get
        {
            return _form.IsEnabled;
        }
        set
        {
            _form.IsEnabled = value;
        }
    }

    public bool UserDismissed
    {
        get
        {
            return _form.UserDismissed;
        }
        set
        {
            _form.UserDismissed = value;
        }
    }

    public AutoCompletionItem SelectedItem
    {
        get
        {
            return _selectedItem;
        }
        set
        {
            if (_selectedItem == value)
                return;

            _selectedItem = value;
        }
    }

    public Font Font
    {
        get
        {
            return _form.ListBoxFont;
        }
        set
        {
            _form.ListBoxFont = value;
        }
    }

    public int MinimumLines
    {
        get
        {
            return _form.MinimumLines;
        }
        set
        {
            _form.MinimumLines = value;
        }
    }

    public int MaximumLines
    {
        get
        {
            return _form.MaximumLines;
        }
        set
        {
            _form.MaximumLines = value;
        }
    }

    public int ImageMargin
    {
        get
        {
            return _form.ImageMargin;
        }
        set
        {
            _form.ImageMargin = value;
        }
    }

    public Padding Margin
    {
        get
        {
            return _form.ListBoxMargin;
        }
        set
        {
            _form.ListBoxMargin = value;
        }
    }

    public FormBorderStyle BorderStyle
    {
        get
        {
            return _form.FormBorderStyle;
        }
        set
        {
            _form.FormBorderStyle = value;
        }
    }

    public ImageList Images
    {
        get
        {
            return _form.Images;
        }
        set
        {
            _form.Images = value;
        }
    }

    public IList<AutoCompletionItem> Items
    {
        get
        {
            return _items;
        }
    }

    public void Hide()
    {
        _form.HideList();
    }

    public void Show()
    {
        _form.ShowList();
    }

    public void Dispose()
    {
        if (_form != null)
        {
            _form.Dispose();
            _form = null;
        }
    }

    private void OnTextChanged(object sender, EventArgs e)
    {
        EventHandler handler = TextChanged;
        if (handler != null)
        {
            handler(sender, e);
        }
    }

    public class AutoCompletionItem
    {
        public AutoCompletionItem()
            : this(null)
        {
        }

        public AutoCompletionItem(string text)
            : this(text, -1)
        {
        }

        public AutoCompletionItem(string text, int imageIndex)
            :this(text, null, imageIndex)
        {
        }

        public AutoCompletionItem(string text, string toolTip, int imageIndex)
            :this(text, toolTip, null, imageIndex)
        {
        }

        public AutoCompletionItem(string text, string toolTip, string toolTipTitle, int imageIndex)
        {
            if (text == null)
            {
                text = string.Empty;
            }
            Text = text;
            ToolTip = toolTip;
            ImageIndex = imageIndex;
            ToolTipTitle = toolTipTitle;
        }

        public string Text { get; set; }
        public string ToolTip { get; set; }
        public string ToolTipTitle { get; set; }
        public int ImageIndex { get; set; }
    }

    private class AutoCompleteForm : Form
    {
        private readonly ImageListBox _listBox;
        private TextBoxBase _textBox;
        private bool _isEnabled;
        private readonly AutoCompletion _autoCompletion;

        [DllImport("user32.dll")]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

        [DllImport("user32.dll")]
        private static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);

        [DllImport("user32.dll")]
        private static extern int MsgWaitForMultipleObjectsEx(int nCount, IntPtr pHandles, int dwMilliseconds, int dwWakeMask, int dwFlags);

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        private const int GWL_HWNDPARENT = -8;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_KEYUP = 0x0101;
        private const int WM_SIZING = 0x0214;
        private const int WM_NCHITTEST = 0x0084;
        private const int HTNOWHERE = 0;
        private const int HTLEFT = 10;
        private const int HTTOP = 12;
        private const int HTTOPLEFT = 13;
        private const int HTTOPRIGHT = 14;
        private const int HTBOTTOMLEFT = 16;
        private const int MWMO_INPUTAVAILABLE = 0x0004;

        private class ImageListBox : ListBox
        {
            private ImageList _images;
            private int _imageMargin;
            private readonly AutoCompleteForm _form;
            public readonly ToolTip _toolTip;
            private Point _lastToolTipPoint;

            public ImageListBox(AutoCompleteForm form)
            {
                _form = form;
                BorderStyle = BorderStyle.None;
                SelectionMode = SelectionMode.One;
                DisplayMember = "Text";
                Dock = DockStyle.Fill;
                DrawMode = DrawMode.OwnerDrawFixed;
                _toolTip = new ToolTip();
            }

            protected override void OnSelectedIndexChanged(EventArgs e)
            {
                base.OnSelectedIndexChanged(e);
                if (SelectedIndices.Count == 0)
                    return;

                Rectangle rect = GetItemRectangle(SelectedIndices[0]);
                AutoCompletionItem item = (AutoCompletionItem)Items[SelectedIndices[0]];
                _toolTip.Show(item.ToolTip, this, Width + 2 * _form.BorderSize.Width, rect.Top);
                if (!string.IsNullOrEmpty(item.ToolTipTitle))
                {
                    _toolTip.ToolTipTitle = item.ToolTipTitle;
                }
                _toolTip.ShowAlways = true;
            }

            protected override void OnDoubleClick(EventArgs e)
            {
                _form.Commit(null);
            }

            protected override void OnMouseMove(MouseEventArgs e)
            {
                base.OnMouseMove(e);
                int index = IndexFromPoint(e.Location);
                if ((index >= 0) && (index < Items.Count))
                {
                    AutoCompletionItem item = (AutoCompletionItem)Items[index];
                    if (!string.IsNullOrEmpty(item.ToolTip))
                    {
                        // avoid flickering
                        if ((_toolTip.GetToolTip(this) != item.ToolTip) && (_lastToolTipPoint != e.Location))
                        {
                            _toolTip.SetToolTip(this, item.ToolTip);
                            if (!string.IsNullOrEmpty(item.ToolTipTitle))
                            {
                                _toolTip.ToolTipTitle = item.ToolTipTitle;
                            }
                            _lastToolTipPoint = e.Location;
                        }
                    }
                }
            }

            protected override void WndProc(ref Message m)
            {
                // we need this to track the TAB character
                if ((m.Msg == WM_KEYUP) && (m.WParam.ToInt32() == 9))
                {
                    _form.OnTabPressed();
                    m.Result = new IntPtr(1); // handled
                    return;
                }
                base.WndProc(ref m);
            }

            public int ImageMargin
            {
                get
                {
                    return _imageMargin;
                }
                set
                {
                    if (_imageMargin == value)
                        return;

                    _imageMargin = value;
                    Invalidate();
                }
            }

            public ImageList Images
            {
                get
                {
                    return _images;
                }
                set
                {
                    if (_images == value)
                        return;

                    _images = value;
                    if (_images != null)
                    {
                        ItemHeight = _images.ImageSize.Height + Margin.Vertical;
                    }
                    Invalidate();
                }
            }

            protected override void OnDrawItem(DrawItemEventArgs e)
            {
                if (e.Index < 0)
                    return;

                AutoCompletionItem item = (AutoCompletionItem)Items[e.Index];
                if (_images == null)
                {
                    e.DrawBackground();
                    e.DrawFocusRectangle();
                    using (Brush foreBrush = new SolidBrush(e.ForeColor))
                    {
                        e.Graphics.DrawString(item.Text, e.Font, foreBrush, e.Bounds);
                    }
                    return;
                }

                Rectangle bounds = e.Bounds;

                bounds.X += Margin.Left;
                if (item.ImageIndex >= 0)
                {
                    _images.Draw(e.Graphics, bounds.Left, bounds.Top, item.ImageIndex);
                    bounds.X += _images.ImageSize.Width + _imageMargin;
                }

                using (Brush backBrush = new SolidBrush(e.BackColor))
                {
                    e.Graphics.FillRectangle(backBrush, bounds);
                }

                if (((e.State & DrawItemState.Focus) == DrawItemState.Focus) && ((e.State & DrawItemState.NoFocusRect) != DrawItemState.NoFocusRect))
                {
                    ControlPaint.DrawFocusRectangle(e.Graphics, bounds, ForeColor, BackColor);
                }

                bounds.Y += Margin.Top;
                using (Brush foreBrush = new SolidBrush(e.ForeColor))
                {
                    e.Graphics.DrawString(item.Text, e.Font, foreBrush, bounds);
                }
            }
        }

        public AutoCompleteForm(AutoCompletion autoCompletion)
        {
            _autoCompletion = autoCompletion;
            ShowInTaskbar = false;
            ControlBox = false;
            MinimizeBox = false;
            MaximizeBox = false;
            Text = string.Empty;
            AutoScaleMode = AutoScaleMode.None;
            _listBox = new ImageListBox(this);
            _listBox.KeyDown += OnListBoxKeyDown;
            Controls.Add(_listBox);
            DockPadding.All = 0;
        }

        public bool UserDismissed { get; set; }
        public int MaximumLines { get; set; }
        public int MinimumLines { get; set; }

        public bool IsEnabled
        {
            get
            {
                return _isEnabled;
            }
            set
            {
                if (_isEnabled != value)
                {
                    _isEnabled = value;
                    if (!_isEnabled)
                    {
                        HideList();
                    }
                }
            }
        }

        public Font ListBoxFont
        {
            get
            {
                return _listBox.Font;
            }
            set
            {
                _listBox.Font = value;
            }
        }

        public Padding ListBoxMargin
        {
            get
            {
                return _listBox.Margin;
            }
            set
            {
                _listBox.Margin = value;
            }
        }

        public int ImageMargin
        {
            get
            {
                return _listBox.ImageMargin;
            }
            set
            {
                _listBox.ImageMargin = value;
            }
        }

        public ImageList Images
        {
            get
            {
                return _listBox.Images;
            }
            set
            {
                _listBox.Images = value;
            }
        }

        private Size BorderSize
        {
            get
            {
                Size size = Size - ClientSize;
                return new Size(size.Width / 2, size.Height / 2);
            }
        }

        private void OnTabPressed()
        {
            Commit(null);
        }

        private static bool PassThru(KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Up || e.KeyCode == Keys.Down || e.KeyCode == Keys.PageUp || e.KeyCode == Keys.PageDown)
                return false;

            return true;
        }

        private void OnListBoxKeyDown(object sender, KeyEventArgs e)
        {
            switch (e.KeyCode)
            {
                case Keys.Escape:
                    UserDismissed = true;
                    HideList();
                    return;

                case Keys.Return:
                    Commit(null);
                    return;

                case Keys.OemPeriod:
                case Keys.Decimal:
                    UserDismissed = false;
                    if (_listBox.SelectedItem == null)
                    {
                        // repost
                        PostMessage(_autoCompletion._form._textBox.Handle, WM_KEYDOWN, new IntPtr((int)e.KeyData), IntPtr.Zero);
                        return;
                    }
                    Commit(".");
                    return;

                default:
                    if (e.KeyCode == Keys.Back)
                    {
                        CaptureOriginalText();
                    }

                    if (PassThru(e))
                    {
                        PostMessage(_autoCompletion._form._textBox.Handle, WM_KEYDOWN, new IntPtr((int)e.KeyData), IntPtr.Zero);
                    }
                    return;
            }
        }

        private static Point GetSelectionPoint(TextBoxBase textBox)
        {
            using (Graphics graphics = Graphics.FromHwnd(textBox.Handle))
            {
                string text;
                if (textBox.Text.Length == 0)
                {
                    // use dummy text
                    text = "I";
                }
                else
                {
                    text = textBox.Text.Substring(0, textBox.SelectionStart);
                }
                SizeF size = graphics.MeasureString(text, textBox.Font);
                if (size.Width > textBox.Width)
                {
                    size.Width = textBox.Width;
                }
                return textBox.Parent.PointToScreen(new Point((int)(size.Width + textBox.Location.X), (int)(size.Height + textBox.Location.Y)));
            }
        }

        private bool _raiseTextChanged = true;
        private void Commit(string extra)
        {
            UserDismissed = false;
            if (_listBox.SelectedItem == null)
                return;

            _raiseTextChanged = false;

            string newSelection = ((AutoCompletionItem)_listBox.SelectedItem).Text + extra;

            string textBefore;
            if (_originalSelectionStart > 0)
            {
                textBefore = _originalText.Substring(0, _originalSelectionStart);
                int pos = textBefore.LastIndexOf('.');
                if (pos >= 0)
                {
                    textBefore = textBefore.Substring(0, pos);
                }
                else
                {
                    textBefore = string.Empty;
                }
            }
            else
            {
                textBefore = string.Empty;
            }

            if ((!textBefore.EndsWith(".")) && (!newSelection.EndsWith(".")) && (textBefore.Length > 0))
            {
                textBefore += ".";
            }

            _textBox.Text = textBefore + newSelection;// +textAfter;
            _textBox.SelectionLength = 0;
            _textBox.SelectionStart = _textBox.Text.Length;

            _raiseTextChanged = true;
            CaptureOriginalText();
            HideList();
        }

        public void SetOwner(TextBoxBase textBox)
        {
            if (textBox == null)
                throw new ArgumentNullException("textBox");

            _textBox = textBox;
            _textBox.KeyDown += OnTextBoxKeyDown;
            _textBox.TextChanged += OnTextBoxTextChanged;
        }

        private void OnTextBoxTextChanged(object sender, EventArgs e)
        {
            if (!IsEnabled)
                return;

            if (!_raiseTextChanged)
                return;

            _autoCompletion.OnTextChanged(_textBox, e);
        }

        private void CaptureOriginalText()
        {
            _originalText = _textBox.Text;
            _originalSelectionStart = _textBox.SelectionStart;
            _originalSelectionLength = _textBox.SelectionLength;
        }

        private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
        {
            if (!IsEnabled)
                return;

            if (Visible)
                return;

            CaptureOriginalText();
        }

        public void HideList()
        {
            Visible = false;
        }

        public void ShowList()
        {
            if (_autoCompletion.Items.Count == 0)
                return;

            if ((_textBox.Text.EndsWith(".")) && (!_originalText.EndsWith(".")))
            {
                _originalText += ".";
                _originalSelectionStart++;
            }

            Visible = false;
            _listBox.Items.Clear();
            float maxWidth = 0;
            int height = _autoCompletion.Items.Count * _listBox.ItemHeight;
            using (Graphics graphics = Graphics.FromHwnd(_listBox.Handle))
            {
                foreach (AutoCompletionItem item in _autoCompletion._items)
                {
                    int index = _listBox.Items.Add(item);
                    if (item == _autoCompletion.SelectedItem)
                    {
                        _listBox.SelectedIndex = index;
                    }

                    if (item.Text != null)
                    {
                        SizeF size = graphics.MeasureString(item.Text, _listBox.Font);
                        if (size.Width > maxWidth)
                        {
                            maxWidth = size.Width;  
                        }
                    }
                }
            }

            SetWindowLong(Handle, GWL_HWNDPARENT, _textBox.Handle);

            Point point = GetSelectionPoint(_textBox);
            Size borderSize = BorderSize;
            if (Images != null)
            {
                point.X -= Images.ImageSize.Width;
            }
            point.X -= borderSize.Width + ListBoxMargin.Left + ImageMargin;
            point.Y += 4; // TODO: can we be smarter?

            Location = point;
            int width = (int)maxWidth + ListBoxMargin.Right;

            int minLines = MinimumLines;
            if (minLines < 1)
            {
                minLines = 1;
            }

            int maxLines = MaximumLines;
            if (maxLines < minLines)
            {
                maxLines = minLines;
            }

            height = Math.Min(height, maxLines * _listBox.ItemHeight);
            ClientSize = new Size(width, height);
            MinimumSize = new Size(width, minLines * _listBox.ItemHeight) + borderSize;
            MaximumSize = new Size(width * 2, maxLines * _listBox.ItemHeight);

            Visible = true;
            _listBox.Focus();
            DoModalLoop();
        }

        private string _originalText;
        private int _originalSelectionStart;
        private int _originalSelectionLength;

        private void DoModalLoop()
        {
            while (Visible)
            {
                typeof(Application).InvokeMember("DoEventsModal", BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.InvokeMethod, null, null, null);
                MsgWaitForMultipleObjectsEx(0, IntPtr.Zero, 250, 0xff, MWMO_INPUTAVAILABLE);
            }
        }

        protected override void OnDeactivate(EventArgs e)
        {
            base.OnDeactivate(e);
            _listBox._toolTip.Hide(_listBox);
            HideList();
        }

        protected override void WndProc(ref Message m)
        {
            // prevent resize handle on top & left of window
            if (m.Msg == WM_NCHITTEST)
            {
                base.WndProc(ref m);
                int ht = m.Result.ToInt32();

                // if user hit left or top, pretend he didn't
                if ((ht == HTLEFT) || (ht == HTBOTTOMLEFT) || (ht == HTTOP) || (ht == HTTOPLEFT) || (ht == HTTOPRIGHT))
                {
                    m.Result = new IntPtr(HTNOWHERE);
                }
                return;
            }

            // ensure integral height and maximum size
            if (m.Msg == WM_SIZING)
            {
                RECT rect = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
                int h = rect.Bottom - rect.Top;
                int newh = h;
                if ((h % _listBox.ItemHeight) != 0)
                {
                    newh = ((h + _listBox.ItemHeight) / _listBox.ItemHeight) * _listBox.ItemHeight;
                }

                if (newh > (_listBox.ItemHeight * _listBox.Items.Count))
                {
                    newh = _listBox.ItemHeight * (_listBox.Items.Count + 1);
                }

                rect.Bottom = rect.Top + newh;
                Marshal.StructureToPtr(rect, m.LParam, false);
                m.Result = new IntPtr(1); // handled
                return;
            }

            base.WndProc(ref m);
        }
    }
}

Note: this is an extract from this 100% free component utility: CodeFluent Runtime Client