0
votes

I'm developing an autocomplete user control for WPF using XAML and C#.

I would like to have the pop-up for the suggestions to appear above all controls. Currently my pop up is a ListView . That causes problems since whenever I decide to show it the UI must find a place for it and to do so moves all the controls which are below it further down.

How can I avoid this? I assume I must put it in a layer which is above all of the other controls?

2
Why not put it in a window of its own? - o_weisman
A window for an auto suggestion box? Not quite a good idea. - Christo S. Christov
So is this a code completion type of control? - o_weisman
No, it's a control for which you provide a dataset of items that it can suggest and it works with them. - Christo S. Christov

2 Answers

1
votes

I have written "auto-complete" style controls before by using the WPF Popup control, combined with a textbox. If you use Popup it should appear, as you say, in a layer over the top of everything else. Just use Placement of Bottom to align it to the bottom of the textbox.

Here is an example that I wrote a while ago. Basically it is a text box which, as you type pops up a suggestions popup, and as you type more it refines the options down. You could fairly easily change it to support multi-word auto-complete style code editing situations if you wanted that:

XAML:

<Grid>
    <TextBox x:Name="textBox" 
             Text="{Binding Text, Mode=TwoWay, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:IntelliSenseUserControl}}}" 
             KeyUp="textBox_KeyUp"/>
    <Popup x:Name="popup" 
           Placement="Bottom" 
           PlacementTarget="{Binding ElementName=textBox}" 
           IsOpen="False"
           Width="200" 
           Height="300">
        <ListView x:Name="listView" 
                  ItemsSource="{Binding FilteredItemsSource, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:IntelliSenseUserControl}}}"
                  SelectionChanged="ListView_Selected"/>
    </Popup>
</Grid>

Code-behind:

public partial class IntelliSenseUserControl : UserControl, INotifyPropertyChanged
{
    public IntelliSenseUserControl()
    {
        InitializeComponent();

        DependencyPropertyDescriptor prop = DependencyPropertyDescriptor.FromProperty(ItemsSourceProperty, typeof(IntelliSenseUserControl));
        prop.AddValueChanged(this, ItemsSourceChanged);
    }


    private void ItemsSourceChanged(object sender, EventArgs e)
    {
        FilteredItemsSource = new ListCollectionView((IList)ItemsSource);
        FilteredItemsSource.Filter = (arg) => { return arg == null || string.IsNullOrEmpty(textBox.Text) || arg.ToString().Contains(textBox.Text.Trim()); };
    }


    public string Text
    {
        get { return (string)GetValue(TextProperty); }
        set { SetValue(TextProperty, value); }
    }

    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(IntelliSenseUserControl), new FrameworkPropertyMetadata(null) { BindsTwoWayByDefault = true });


    public object ItemsSource
    {
        get { return (object)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(object), typeof(IntelliSenseUserControl), new PropertyMetadata(null));


    #region Notified Property - FilteredItemsSource (ListCollectionView)
    public ListCollectionView FilteredItemsSource
    {
        get { return filteredItemsSource; }
        set { filteredItemsSource = value; RaisePropertyChanged("FilteredItemsSource"); }
    }

    private ListCollectionView filteredItemsSource;
    #endregion


    private void textBox_KeyUp(object sender, KeyEventArgs e)
    {
        if (e.Key == Key.Return || e.Key == Key.Enter)
        {
            popup.IsOpen = false;
        }
        else
        {
            popup.IsOpen = true;
            FilteredItemsSource.Refresh();
        }
    }

    private void UserControl_LostFocus(object sender, RoutedEventArgs e)
    {
        popup.IsOpen = false;
    }

    private void ListView_Selected(object sender, RoutedEventArgs e)
    {
        if (listView.SelectedItem != null)
        {
            Text = listView.SelectedItem.ToString().Trim();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    void RaisePropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }
}
1
votes

If your Window's content container is a Grid, you can simply do something like

<ListBox Grid.RowSpawn="99" Grid.ColumnSpan="99"/>

to "simulate" an absolute position. You then just have to set its position with Margin, HorizontalAlignment and VerticalAlignment so it lays around the desired control.