Created
August 25, 2016 07:18
-
-
Save gulbanana/cc5f611c65c5f7e33123b2586b77ae35 to your computer and use it in GitHub Desktop.
SuggestionBox control
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class SuggestionBox : Control | |
{ | |
static SuggestionBox() { DefaultStyleKeyProperty.OverrideMetadata(typeof(SuggestionBox), new FrameworkPropertyMetadata(typeof(SuggestionBox))); } | |
public static DependencyProperty SelectedItemProperty = | |
DependencyProperty.Register("SelectedItem", typeof(string), typeof(SuggestionBox), new FrameworkPropertyMetadata("", FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (d, bv) => (d as SuggestionBox).OnSelectedItemChanged())); | |
public string SelectedItem | |
{ | |
get { return (string)GetValue(SelectedItemProperty); } | |
set { SetValue(SelectedItemProperty, value); } | |
} | |
public static DependencyProperty ItemsSourceProperty = | |
DependencyProperty.Register("ItemsSource", typeof(IEnumerable<string>), typeof(SuggestionBox)); | |
public IEnumerable<string> ItemsSource | |
{ | |
get { return (IEnumerable<string>)GetValue(ItemsSourceProperty); } | |
set { SetValue(ItemsSourceProperty, value); } | |
} | |
public static DependencyProperty SelectedBackgroundBrushProperty = | |
DependencyProperty.Register("SelectedBackgroundBrush", typeof(Brush), typeof(SuggestionBox)); | |
public Brush SelectedBackgroundBrush | |
{ | |
get { return (Brush)GetValue(SelectedBackgroundBrushProperty); } | |
set { SetValue(SelectedBackgroundBrushProperty, value); } | |
} | |
public static DependencyProperty SelectOnFocusLossProperty = | |
DependencyProperty.Register("SelectOnFocusLoss", typeof(bool), typeof(SuggestionBox), new FrameworkPropertyMetadata(false)); | |
public bool SelectOnFocusLoss | |
{ | |
get { return (bool)GetValue(SelectOnFocusLossProperty); } | |
set { SetValue(SelectOnFocusLossProperty, value); } | |
} | |
private TextBox PART_TextBox; | |
private ItemsControl PART_ItemsControl; | |
private Popup PART_Popup; | |
public IReadOnlyList<string> CurrentSuggestions { get; private set; } | |
public IReadOnlyList<string> FilteredSuggestions { get; private set; } | |
public int SelectedIndex { get; private set; } | |
public SuggestionBox() | |
{ | |
CurrentSuggestions = new string[] { }; | |
SelectedIndex = 0; | |
} | |
private void UpdateSuggestions() | |
{ | |
if (CurrentSuggestions.Any(s => object.ReferenceEquals(s, SelectedItem))) | |
{ | |
if (PART_Popup != null) PART_Popup.IsOpen = false; | |
} | |
else | |
{ | |
if (PART_Popup != null) PART_Popup.IsOpen = true; | |
FilteredSuggestions = CurrentSuggestions.Where(s => s.ToLower().Contains(SelectedItem.ToLower())).ToList(); | |
SelectedIndex = Math.Min(SelectedIndex, FilteredSuggestions.Count() - 1); | |
if (PART_ItemsControl != null) PART_ItemsControl.ItemsSource = FilteredSuggestions.Select((s, i) => new SuggestionBoxItem | |
{ | |
Text = s, | |
Background = (i == SelectedIndex) ? SelectedBackgroundBrush : null, | |
OnMouseDown = new DelegateCommand<SuggestionBoxItem>(OnItemClicked) | |
}); | |
} | |
} | |
public override void OnApplyTemplate() | |
{ | |
base.OnApplyTemplate(); | |
PART_TextBox = Template.FindName(nameof(PART_TextBox), this) as TextBox; | |
PART_TextBox.TextChanged += OnTextChanged; | |
PART_ItemsControl = Template.FindName(nameof(PART_ItemsControl), this) as ItemsControl; | |
PART_Popup = Template.FindName(nameof(PART_Popup), this) as Popup; | |
} | |
private void OnTextChanged(object sender, TextChangedEventArgs e) | |
{ | |
SelectedItem = PART_TextBox.Text; | |
} | |
private void OnItemClicked(SuggestionBoxItem s) | |
{ | |
SelectedItem = s.Text; | |
} | |
private void OnSelectedItemChanged() | |
{ | |
UpdateSuggestions(); | |
} | |
protected override void OnGotKeyboardFocus(KeyboardFocusChangedEventArgs e) | |
{ | |
CurrentSuggestions = ItemsSource.ToList(); | |
SelectedIndex = 0; | |
UpdateSuggestions(); | |
} | |
public void SetGotFocus() => OnGotKeyboardFocus(null); | |
protected override void OnLostKeyboardFocus(KeyboardFocusChangedEventArgs e) | |
{ | |
if (PART_Popup != null) PART_Popup.IsOpen = false; | |
if (SelectOnFocusLoss) | |
{ | |
SelectedItem = FilteredSuggestions.ElementAtOrDefault(SelectedIndex) ?? SelectedItem; | |
} | |
} | |
public void SetLostFocus() => OnLostKeyboardFocus(null); | |
protected override void OnPreviewKeyDown(KeyEventArgs e) | |
{ | |
base.OnPreviewKeyDown(e); | |
if (e.Key == Key.Down && SelectedIndex < CurrentSuggestions.Count - 1) | |
{ | |
SelectedIndex++; | |
UpdateSuggestions(); | |
} | |
else if (e.Key == Key.Up && SelectedIndex > 0) | |
{ | |
SelectedIndex--; | |
UpdateSuggestions(); | |
} | |
else if (e.Key == Key.Enter) | |
{ | |
SelectedItem = FilteredSuggestions.ElementAtOrDefault(SelectedIndex) ?? SelectedItem; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<ResourceDictionary x:Class="The.WPF.Controls.SuggestionBoxStyle" x:ClassModifier="internal" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
xmlns:controls="clr-namespace:The.WPF.Controls"> | |
<Style TargetType="{x:Type controls:SuggestionBox}"> | |
<Setter Property="IsTabStop" Value="False"/> | |
<Setter Property="SelectedBackgroundBrush" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/> | |
<Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/> | |
<Setter Property="BorderThickness" Value="1,0,1,1"/> | |
<Setter Property="BorderBrush" Value="Black"/> | |
<Setter Property="Template"> | |
<Setter.Value> | |
<ControlTemplate TargetType="{x:Type controls:SuggestionBox}"> | |
<StackPanel Orientation="Vertical"> | |
<TextBox x:Name="PART_TextBox" Text="{TemplateBinding SelectedItem}"/> | |
<Popup x:Name="PART_Popup" IsOpen="False" Visibility="Collapsed" AllowsTransparency="True" Width="{Binding ActualWidth,ElementName=PART_TextBox}"> | |
<ItemsControl x:Name="PART_ItemsControl" IsTabStop="False" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}"> | |
<ItemsControl.ItemTemplate> | |
<DataTemplate> | |
<Grid MouseDown="Item_MouseDown" Tag="{Binding OnMouseDown}" Height="22"> | |
<Border Background="{Binding Background}" Opacity="0.25"/> | |
<TextBlock Text="{Binding Text}" VerticalAlignment="Center"/> | |
</Grid> | |
</DataTemplate> | |
</ItemsControl.ItemTemplate> | |
</ItemsControl> | |
</Popup> | |
</StackPanel> | |
<ControlTemplate.Triggers> | |
<Trigger Property="IsKeyboardFocusWithin" Value="False"> | |
<Setter TargetName="PART_ItemsControl" Property="BorderThickness" Value="0"/> | |
</Trigger> | |
</ControlTemplate.Triggers> | |
</ControlTemplate> | |
</Setter.Value> | |
</Setter> | |
</Style> | |
</ResourceDictionary> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment