I also had wanted to have this feature once but never attempted to create one. This time I've taken a shot and it's obviously a goal. Here's what I've in my Themes\Generic.xaml
for the AutoCompleteBox
:
<Style TargetType="{x:Type local:AutoCompleteBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AutoCompleteBox}">
<Grid>
<TextBox x:Name="inputText" Text="{TemplateBinding Text}"/>
<Popup IsOpen="{Binding IsSuggestionVisible, RelativeSource={RelativeSource TemplatedParent}}"
Width="{Binding ElementName=inputText, Path=ActualWidth}"
MaxHeight="100"
Placement="Bottom">
<ListBox x:Name="suggestionBox"
BorderThickness="1"
BorderBrush="DodgerBlue"
IsSynchronizedWithCurrentItem="True"
ItemsSource="{TemplateBinding ItemsSource}">
<ListBox.Resources>
<Style TargetType="ListBox">
<Style.Triggers>
<DataTrigger Binding="{Binding HasItems, RelativeSource={RelativeSource Self}}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.InputBindings>
<KeyBinding Key="Return"
Command="{Binding SelectItem, RelativeSource={RelativeSource TemplatedParent}}"
CommandParameter="{Binding ElementName=suggestionBox, Path=SelectedItem}"/>
</ListBox.InputBindings>
</ListBox>
</Popup>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
and in AutoCompleteBox.cs
I've these:
public class AutoCompleteBox : Control
{
TextBox inputText;
ListBox suggestionBox;
ICollectionView suggestionView;
static AutoCompleteBox()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(AutoCompleteBox), new FrameworkPropertyMetadata(typeof(AutoCompleteBox)));
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
inputText = (TextBox)GetTemplateChild("inputText");
suggestionBox = (ListBox)GetTemplateChild("suggestionBox");
suggestionView = CollectionViewSource.GetDefaultView(ItemsSource);
suggestionView.Filter = filterSuggestion;
inputText.TextChanged += updateSuggestion;
inputText.KeyUp += onKeyUp;
SelectItem = new Command(selectItem, (o) => IsSuggestionVisible);
}
void onKeyUp(object sender, KeyEventArgs e)
{
if (e.Key == Key.Down)
{
if (IsSuggestionVisible)
{
suggestionView.MoveCurrentToFirst();
suggestionBox.Focus();
//((ListBoxItem)suggestionBox.SelectedItem).Focus();
}
}
}
bool filterSuggestion(object o) => (((string)o).ToLower()).Contains(inputText.Text.ToLower());
void updateSuggestion(object sender, TextChangedEventArgs e)
{
suggestionView.Refresh();
IsSuggestionVisible = string.IsNullOrWhiteSpace(inputText.Text) ? false : true;
}
void selectItem(object o)
{
var text = (string)o;
inputText.Text = text;
inputText.CaretIndex = text.Length + 1;
inputText.Focus();
IsSuggestionVisible = false;
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(AutoCompleteBox), new PropertyMetadata(null));
public IEnumerable ItemsSource
{
get { return (IEnumerable)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
// Using a DependencyProperty as the backing store for ItemsSource. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(AutoCompleteBox), new PropertyMetadata(null));
public bool IsSuggestionVisible
{
get { return (bool)GetValue(IsSuggestionVisibleProperty); }
set { SetValue(IsSuggestionVisibleProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSuggestionVisible. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSuggestionVisibleProperty =
DependencyProperty.Register("IsSuggestionVisible", typeof(bool), typeof(AutoCompleteBox), new PropertyMetadata(false));
public ICommand SelectItem
{
get { return (ICommand)GetValue(SelectItemProperty); }
set { SetValue(SelectItemProperty, value); }
}
// Using a DependencyProperty as the backing store for SelectItem. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SelectItemProperty =
DependencyProperty.Register("SelectItem", typeof(ICommand), typeof(AutoCompleteBox), new PropertyMetadata(null));
}
The problem I'm having with this is Focus
. When I press the DownArrow, it first selects the ListBox
and then the focus moves to the SelectedItem
so basically I've to press DownArrow twice to get to the SelectedItem:
in onKeyUp
I've tried ((ListBoxItem)suggestionBox.SelectedItem).Focus();
to take focus to the SelectedItem directly BUT that crashes the App. How to get to the SelectedItem directly?
----
EDIT
this in onKeyUp
:
if (IsSuggestionVisible)
{
suggestionView.MoveCurrentToFirst();
((ListBoxItem)suggestionBox.ItemContainerGenerator.ContainerFromItem(suggestionView.CurrentItem)).Focus();
}
solves the Focus issue.