Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A combo-box providing a resizable file name completion list and drop-down list with a history

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!

like image 215
Vladimir Reshetnikov Avatar asked Jul 11 '13 19:07

Vladimir Reshetnikov


People also ask

Which of the following can be used to get a combo box or dropdown?

Answer: We can use “<select> tag” to create combo box. Explanation: Combo box is used to display the drop down list of the options from which we can able to select.

What is combo box and list box?

Generally, a combo box is appropriate when there is a list of suggested choices, and a list box is appropriate when you want to limit input to what is on the list. A combo box contains a text box field, so choices not on the list can be typed in. The exception is when the DropDownStyle property is set to DropDownList.

Which property makes the ComboBox to be displayed with a textbox and the list box which doesn't drop-down?

To make a combo box editable, set the IsEditable property to true.

What is combo box content control in Word?

A combo box is a text box with a list box attached. This type of control enables users to select a predefined value in a list or type their own value in the text box portion of the control. The list is hidden until the user clicks the arrow next to the box.


2 Answers

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.

like image 128
Anatoliy Nikolaev Avatar answered Sep 19 '22 18:09

Anatoliy Nikolaev


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".

like image 24
Viacheslav Ivanov Avatar answered Sep 18 '22 18:09

Viacheslav Ivanov