Очень часто, при написании приложений, возникает необходимость сделать текстовое поле с выпадающими подсказками (например, как у яндекса или гугла при вводе в строку поиска). В статье представлен один из способов реализации текстового поля с выпадающими подсказками на языке программирования C# WPF.
Структура проекта будет следующей:
В директории "Controls" будет находится пользовательский элемент управления, реализующий поставленную задачу, Window1 - главное окно приложения, TestModel - тестовая модель.
Итак, сначала создадим пользовательский элемент управления, назовем его TextBoxDropDownHintControl, поместим его в каталог Controls. XAML разметка будет следующией:
<UserControl x:Class="TextBoxDropDownHint.Controls.TextBoxDropDownHintControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBox Panel.ZIndex="2"
x:Name="tbxInputData"></TextBox>
<ComboBox
SelectionChanged="LbList_SelectionChanged"
x:Name="lbList"
Height="25"
Panel.ZIndex="1">
<ComboBox.ItemContainerStyle>
<Style TargetType="ComboBoxItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ComboBoxItem">
<Grid x:Name="gd" Width="Auto" ShowGridLines="False">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="{Binding DisplayMemberPath}"/>
<TextBlock
Margin="0, 0, 5, 0"
Grid.Column="1"
Text="{Binding SecondColumnValue}"
HorizontalAlignment="Right"/>
<ContentPresenter></ContentPresenter>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="ComboBoxItem.IsSelected" Value="True">
<Setter TargetName="gd" Property="Background" Value="Gray"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<Trigger Property="ComboBoxItem.IsMouseOver" Value="True">
<Setter TargetName="gd" Property="Background" Value="Blue"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
<Trigger Property="ComboBoxItem.IsHighlighted" Value="True">
<Setter TargetName="gd" Property="Background" Value="Blue"></Setter>
<Setter TargetName="gd" Property="TextElement.Foreground" Value="White"></Setter>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ComboBox.ItemContainerStyle>
</ComboBox>
</Grid>
</UserControl>
В ней мы создаем текстовое поле (TextBox) и Выпадающий список (ComboBox), которы спрятан за текстовым полем.
C# код будет следующим:
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace TextBoxDropDownHint.Controls
{
public class SelectionChanged
{
public object Value
{
get;
private set;
}
public SelectionChanged(object v)
{
Value = v;
}
}
public partial class TextBoxDropDownHintControl : UserControl
{
public delegate void SelectedValueChanged (object sender, SelectionChanged e);
public event SelectedValueChanged OnSelect;
public delegate void _TextChanged (object sender, TextChangedEventArgs e);
public event _TextChanged TextChanged;
IEnumerable<object> itemsSource = null;
ObservableCollection<object> ISCollection = new ObservableCollection<object>();
public TextBoxDropDownHintControl()
{
InitializeComponent();
lbList.ItemsSource = ISCollection;
tbxInputData.TextChanged += delegate(object sender, TextChangedEventArgs e)
{
ISCollection.Clear();
lbList.IsDropDownOpen = false;
if(tbxInputData.Text == "") return;
if(FilterItems())
{
lbList.IsDropDownOpen = true;
}
if(TextChanged != null)
{
TextChanged(this, e);
}
};
tbxInputData.PreviewKeyDown += delegate(object sender, KeyEventArgs e)
{
if(e.Key == Key.Down && lbList.IsDropDownOpen == true)
{
lbList.Focus();
}
};
lbList.PreviewKeyDown += delegate(object sender, KeyEventArgs e)
{
if(e.Key == Key.Escape)
{
tbxInputData.Focus();
e.Handled = true;
}
};
}
private bool FilterItems ()
{
if(itemsSource == null) return false;
List<object> collection = (itemsSource).ToList();
for(int i = 0; i < collection.Count; i++)
{
var propertyInfo = collection[i].GetType().GetProperty(lbList.DisplayMemberPath);
string val = propertyInfo.GetValue(collection[i], null).ToString();
if(val.ToLower().IndexOf(tbxInputData.Text.ToLower()) != -1)
{
ISCollection.Add(collection[i]);
}
}
if(ISCollection.Count > 0)
{
return true;
}
return false;
}
void LbList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if(OnSelect != null)
{
OnSelect(this, new SelectionChanged(lbList.SelectedItem));
}
tbxInputData.Text = lbList.Text;
tbxInputData.Focus();
tbxInputData.CaretIndex = tbxInputData.Text.Length;
lbList.IsDropDownOpen = false;
}
public string DisplayMemberPath
{
get
{
return lbList.DisplayMemberPath;
}
set
{
lbList.DisplayMemberPath = value;
}
}
public string SelectedValuePath
{
get
{
return lbList.SelectedValuePath;
}
set
{
lbList.SelectedValuePath = value;
}
}
public Style TextFieldStyle
{
get
{
return tbxInputData.Style;
}
set
{
tbxInputData.Style = value;
}
}
public Style ListStyle
{
get
{
return lbList.Style;
}
set
{
lbList.Style = value;
}
}
public string Text
{
get
{
return tbxInputData.Text;
}
set
{
tbxInputData.Text = value;
lbList.IsDropDownOpen = false;
}
}
public bool IsReadOnly
{
get
{
return tbxInputData.IsReadOnly;
}
set
{
tbxInputData.IsReadOnly = value;
}
}
public new Brush Background
{
get
{
return tbxInputData.Background;
}
set
{
tbxInputData.Background = value;
}
}
public void Clear ()
{
lbList.SelectedIndex = -1;
tbxInputData.Text = "";
}
public TextBox getTextBoxItem ()
{
return tbxInputData;
}
public string CurrentText
{
get
{
return GetValue(CurrentTextProperty).ToString();
}
set
{
SetCurrentTextDependencyProperty(CurrentTextProperty, value);
Text = value;
}
}
public static readonly DependencyProperty CurrentTextProperty = DependencyProperty.Register("CurrentText", typeof(string),
typeof(TextBoxDropDownHintControl), new FrameworkPropertyMetadata(default(string),
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnCurrentTextChanged)));
public event PropertyChangedEventHandler CurrentTextChanged;
void SetCurrentTextDependencyProperty (DependencyProperty property, string value)
{
SetValue(property, value);
if(CurrentTextChanged != null)
{
CurrentTextChanged(this, new PropertyChangedEventArgs(property.ToString()));
}
}
private static void OnCurrentTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBoxDropDownHintControl control = d as TextBoxDropDownHintControl;
if (control == null) return;
control.CurrentText = e.NewValue.ToString();
}
public IEnumerable<object> ItemsSource
{
get
{
return (IEnumerable<object>)GetValue(ItemsSourceProperty);
}
set
{
SetItemsSourceDependencyProperty(ItemsSourceProperty, value);
itemsSource = value;
}
}
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(IEnumerable<object>),
typeof(TextBoxDropDownHintControl), new FrameworkPropertyMetadata(default(IEnumerable<object>),
FrameworkPropertyMetadataOptions.None,
new PropertyChangedCallback(OnItemsSourceChanged)));
public event PropertyChangedEventHandler ItemsSourceChanged;
void SetItemsSourceDependencyProperty (DependencyProperty property, IEnumerable<object> value)
{
SetValue(property, value);
if(ItemsSourceChanged != null)
{
ItemsSourceChanged(this, new PropertyChangedEventArgs(property.ToString()));
}
}
private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
TextBoxDropDownHintControl control = d as TextBoxDropDownHintControl;
if (control == null) return;
control.ItemsSource = (IEnumerable<object>)e.NewValue;
}
}
}
В нем мы настраиваем поведение элементов и привязки данных.
Далее создадим класс модели:
using System;
namespace TextBoxDropDownHint
{
public class TestModel
{
public int Id {get;set;}
public string Name {get;set;}
public string Description {get;set;}
public TestModel() { }
}
}
XAML разметка главного окна приложения будет следующей:
<Window x:Class="TextBoxDropDownHint.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="TextBoxDropDownHint" Height="300" Width="300"
xmlns:controls="clr-namespace:TextBoxDropDownHint.Controls">
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="35"></RowDefinition>
<RowDefinition Height="35"></RowDefinition>
<RowDefinition Height="35"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100"></ColumnDefinition>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<controls:TextBoxDropDownHintControl
Grid.Row="0" Grid.ColumnSpan="2"
ItemsSource="{Binding TestCollection}"
HorizontalAlignment="Stretch"
SelectedValuePath="Id"
DisplayMemberPath="Name"
OnSelect="TextBoxDropDownHintControl_OnSelect"
Height="30" VerticalAlignment="Top">
</controls:TextBoxDropDownHintControl>
<Label Content="Id" Grid.Row="1" Grid.Column="0"></Label>
<TextBox Margin="0, 5" VerticalContentAlignment="Center" x:Name="tbxId" Grid.Row="1" Grid.Column="1"></TextBox>
<Label Content="Name" Grid.Row="2" Grid.Column="0"></Label>
<TextBox Margin="0, 5" VerticalContentAlignment="Center" x:Name="tbxName" Grid.Row="2" Grid.Column="1"></TextBox>
<Label Content="Description" Grid.Row="3" Grid.Column="0"></Label>
<TextBox Margin="0, 5" VerticalContentAlignment="Center" x:Name="tbxDescription" Grid.Row="3" Grid.Column="1"></TextBox>
</Grid>
</Window>
Исходный код главного окна приложения представлен ниже:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
namespace TextBoxDropDownHint
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List<TestModel> coll = new List<TestModel>();
coll.Add(new TestModel() {Id = 1, Name = "Москва", Description = "Описание города"});
coll.Add(new TestModel() {Id = 2, Name = "Санкт-Петербург", Description = "Описание города"});
coll.Add(new TestModel() {Id = 3, Name = "Екатеринбург", Description = "Описание города"});
coll.Add(new TestModel() {Id = 4, Name = "Уфа", Description = "Описание города"});
coll.Add(new TestModel() {Id = 5, Name = "Пенза", Description = "Описание города"});
DataContext = new {TestCollection = coll};
}
void TextBoxDropDownHintControl_OnSelect(object sender, TextBoxDropDownHint.Controls.SelectionChanged e)
{
if(e.Value != null)
{
TestModel model = e.Value as TestModel;
if(model == null) return;
tbxId.Text = model.Id.ToString();
tbxName.Text = model.Name;
tbxDescription.Text = model.Description;
}
}
}
}
Результаты работы программы представлены на рисунках 1-3