Всем не раз встречались большие списки, в которых надо было найти и выбрать один или несколько элементов (например, поиск конкретного отеля в городе). Хорошо, если к такому списку прикручен поиск, но, к сожалению, так бывает далеко не всегда.
Разрабатывая один из проектов на .Net, столкнулся со следующей задачей — необходимо было в WPF реализовать поиск элементов (элемент представлял из себя строку) в списке ListBox с подсветкой введенного текста для поиска. После продолжительного поиска в гугле, готовых вариантов решения найдено не было, принялся за реализацию данной задачи.
Минимальные знания, которые потребуются: WPF, C#, LINQ.
Первым делом — составим интерфейс будущего контролла, в данном случае — это будет TextBox, прямо под которым находится ListBox. Элементом ListBox'а будет являться CheckBox, в теле которого находится два TextBlock'а. Хотя их там будет даже 3, но об этом позже. CheckBox — потому что требуется выбрать несколько элементов, при этом выбранные элементы будем поднимать вверх списка (чтобы проще было их найти в огромном списке). Почему 2 TextBlock'a: т.к. стоит задача подсветить найденный кусочек текста во всей фразе, то нам придется разбивать один из TextBlock на Inlines — составные части, у одной из которых надо будет подсветить Background. Второй же TextBlock будет отображать полностью фразу, и будет находиться поверх первого TextBlock'a.
Для примера, в качестве элементов списка, возьмем системные шрифты — получается как раз достаточно большой список объектов (у меня — 257, но тестировал и на большем количестве). Чтобы увидеть, как шрифт применяется, дополним тело CheckBox'а ещё одним TextBlock'ом, в котором разместим фразу «ABCD АБВГ 1234» и будем применять к ней шрифт.
Код шаблона ListBoxItem
Как видно в коде, TextBlock с именем textBackground разбит на 2 части, на два Inlines: textBefore — текст, который будет перед выделением, и textSelect — текст, который будет выделен. Полностью текст шрифта выводится в TextBlock с именем textContent. К TextBlock'у c именем textInFont применяется начертание шрифта, чтобы можно было увидеть, как шрифт отобразится на русских и английских символах, а так же на цифрах.
ListBox будет описывается следующим образом
Таким образом, разместили TextBox и под ним ListBox, применив к элементам ListBox шаблон, описанный выше. Подписавшись на необходимые события, переходим к следующей части.
Во первых, необходим класс, который будет содержать все необходимые элементы для привязки данных. Для примера, назовем его Cust
Теперь необходимо создать две коллекции — в первой будем хранить полный список объектов, во втором — только отфильтрованные элементы. И сразу же напишем функцию инициализации контролла
Теперь мы можем наблюдать вот такой контролл:
Теперь самое интересное — фильтрация и подсветка. Для фильтрации напишем следующую функцию:
В случае, если в TextBox'e не было ничего введено, то выбранные элементы автоматически поднимаются в вверх, если же пользователь начал вводить, то после каждого изменения текста в TextBox вызывается функция FilterItems, в которую передается текущей текст в TextBox'е, после чего происходит фильтрация по заданному условию основной коллекции, результат записывается во вторую коллекцию, с которой уже и связывается DataContext ListBox'a. Так же, во второй коллекции для каждого элемента задается TextBefore и TextSelect, к которым как раз привязаны соответствующие по названиям TextBlock'и в шаблоне ListBoxItem.
Сортировка второй коллекции производится сначала по первому вхождению найденного текста, и только потом — вхождение этого текста в остальной части фразы.
При выборе шрифта, либо отмене выбора — происходит событие либо checkControl_Checked, либо checkControl_Unchecked соответственно.
Для случая, если понадобится выбрать все элементы — реализовал возможность выделения всех элементов
В итоге — получился вот такой контролл, в котором быстро можно найти один или несколько элементов. При этом — подсвечивается найденный текст в элементе.
Спасибо за внимание. Надеюсь, статья была полезна и интересна.
P.S. исходники можно взять тут narod.ru либо dropbox
Разрабатывая один из проектов на .Net, столкнулся со следующей задачей — необходимо было в WPF реализовать поиск элементов (элемент представлял из себя строку) в списке ListBox с подсветкой введенного текста для поиска. После продолжительного поиска в гугле, готовых вариантов решения найдено не было, принялся за реализацию данной задачи.
Минимальные знания, которые потребуются: WPF, C#, LINQ.
Создание интерфейса
Первым делом — составим интерфейс будущего контролла, в данном случае — это будет TextBox, прямо под которым находится ListBox. Элементом ListBox'а будет являться CheckBox, в теле которого находится два TextBlock'а. Хотя их там будет даже 3, но об этом позже. CheckBox — потому что требуется выбрать несколько элементов, при этом выбранные элементы будем поднимать вверх списка (чтобы проще было их найти в огромном списке). Почему 2 TextBlock'a: т.к. стоит задача подсветить найденный кусочек текста во всей фразе, то нам придется разбивать один из TextBlock на Inlines — составные части, у одной из которых надо будет подсветить Background. Второй же TextBlock будет отображать полностью фразу, и будет находиться поверх первого TextBlock'a.
Для примера, в качестве элементов списка, возьмем системные шрифты — получается как раз достаточно большой список объектов (у меня — 257, но тестировал и на большем количестве). Чтобы увидеть, как шрифт применяется, дополним тело CheckBox'а ещё одним TextBlock'ом, в котором разместим фразу «ABCD АБВГ 1234» и будем применять к ней шрифт.
Код шаблона ListBoxItem
<DataTemplate x:Key="ListItemTemplate" >
<Grid x:Name="gridBasic"
Background="{Binding colorGround}"
Visibility="{Binding visibility}">
<CheckBox Name="checkControl"
Margin="5"
IsChecked="{Binding isCheck}"
Checked="checkControl_Checked"
Unchecked="checkControl_Unchecked"
HorizontalContentAlignment="Stretch">
<Grid HorizontalAlignment="Stretch">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<Grid>
<TextBlock Name="textBackground"
Foreground="#00000000">
<TextBlock.Inlines >
<TextBlock Name="textBefore"
Text="{Binding TextBefore}"/><TextBlock
Name="textSelect"
Text="{Binding TextSelect}"
Background="LightBlue"/>
</TextBlock.Inlines>
</TextBlock>
<TextBlock Name="textContent"
Text="{Binding Namefont}"/>
</Grid>
<TextBlock Grid.Column="1"
Name="textInFont"
FontFamily="{Binding Fontstyle}"
Text="Abcd Абвг 123"
HorizontalAlignment="Right"/>
</Grid>
</CheckBox>
</Grid>
</DataTemplate>
Как видно в коде, TextBlock с именем textBackground разбит на 2 части, на два Inlines: textBefore — текст, который будет перед выделением, и textSelect — текст, который будет выделен. Полностью текст шрифта выводится в TextBlock с именем textContent. К TextBlock'у c именем textInFont применяется начертание шрифта, чтобы можно было увидеть, как шрифт отобразится на русских и английских символах, а так же на цифрах.
ListBox будет описывается следующим образом
<Grid Grid.Row="2">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0"
Name="textBoxSearch"
TextChanged="TextBox_TextChanged"/>
<ListBox Name="listBox"
ItemsSource="{Binding}"
Grid.Row="1"
HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource ListItemTemplate}"
SelectionChanged="listBox_SelectionChanged"/>
</Grid>
Таким образом, разместили TextBox и под ним ListBox, применив к элементам ListBox шаблон, описанный выше. Подписавшись на необходимые события, переходим к следующей части.
Программная составляющая
Во первых, необходим класс, который будет содержать все необходимые элементы для привязки данных. Для примера, назовем его Cust
public class Cust
{
/// <summary>
/// название шрифта
/// </summary>
public string Namefont { get; set; }
/// <summary>
/// начертание шрифта
/// </summary>
public FontFamily Fontstyle { get; set; }
/// <summary>
/// выбран шрифт или нет
/// </summary>
public bool isCheck { get; set; }
/// <summary>
/// текст перед выделением
/// </summary>
public String TextBefore { get; set; }
/// <summary>
/// выделенный текст
/// </summary>
public String TextSelect { get; set; }
/// <summary>
/// подсветка выбранного элемента
/// </summary>
public Brush colorGround
{
get
{
if (isCheck) return Brushes.LightSkyBlue;
else return Brushes.White;
}
}
public Cust(String newFonts, bool check)
{
Namefont = newFonts;
Fontstyle = new FontFamily(Namefont);
isCheck = check;
}
}
Теперь необходимо создать две коллекции — в первой будем хранить полный список объектов, во втором — только отфильтрованные элементы. И сразу же напишем функцию инициализации контролла
/// <summary>
/// Коллекция всех элементов
/// </summary>
private List<Cust> _collectionSource = new ObservableCollection<Cust>();
/// <summary>
/// коллекция фильтрованных элементов
/// </summary>
public List<Cust> Collection = new ObservableCollection<Cust>();
public void Init()
{
//достаем системные шрифты, список сразу делаем сортированным по названию
List<String> fontsList = (Fonts.SystemFontFamilies.OrderBy(f => f.Source).Select(f => f.Source)).ToList();
_collectionSource.Clear();
Collection.Clear();
textCol.Text = fontsList.Count.ToString();
for (int i = 0; i < fontsList.Count; i++)
{
_collectionSource.Add(new Cust(fontsList[i], false));
Collection.Add(_collectionSource[i]);
}
listBox.DataContext = Collection;
}
Теперь мы можем наблюдать вот такой контролл:
Теперь самое интересное — фильтрация и подсветка. Для фильтрации напишем следующую функцию:
/// <summary>
/// фильтрация коллекции
/// </summary>
/// <param name="s"></param>
private void FilterItems(string s)
{
if (string.IsNullOrEmpty(textBoxSearch.Text))
{
Collection = new List<Cust>(_collectionSource.OrderBy(i => !i.isCheck));
foreach (Cust item in Collection)
{
item.TextSelect = String.Empty;
}
}
else
{
Collection = new List<Cust>(_collectionSource.OrderBy(i => !i.Namefont.ToLower().StartsWith(s)).
Where(item => item.Namefont.ToLower().Contains(s)));
foreach (Cust item in Collection)
{
item.TextBefore = item.Namefont.Substring(0, item.Namefont.ToLower().IndexOf(s));
item.TextSelect = item.Namefont.Substring(item.Namefont.ToLower().IndexOf(s), s.Length);
}
}
listBox.DataContext = Collection;
}
В случае, если в TextBox'e не было ничего введено, то выбранные элементы автоматически поднимаются в вверх, если же пользователь начал вводить, то после каждого изменения текста в TextBox вызывается функция FilterItems, в которую передается текущей текст в TextBox'е, после чего происходит фильтрация по заданному условию основной коллекции, результат записывается во вторую коллекцию, с которой уже и связывается DataContext ListBox'a. Так же, во второй коллекции для каждого элемента задается TextBefore и TextSelect, к которым как раз привязаны соответствующие по названиям TextBlock'и в шаблоне ListBoxItem.
Сортировка второй коллекции производится сначала по первому вхождению найденного текста, и только потом — вхождение этого текста в остальной части фразы.
При выборе шрифта, либо отмене выбора — происходит событие либо checkControl_Checked, либо checkControl_Unchecked соответственно.
private void checkControl_Checked(object sender, RoutedEventArgs e)
{
FilterItems(textBoxSearch.Text.ToLower());
textCol.Text = _collectionSource.Count(i => i.isCheck).ToString();
}
private void checkControl_Unchecked(object sender, RoutedEventArgs e)
{
FilterItems(textBoxSearch.Text.ToLower());
textCol.Text = _collectionSource.Count(i => i.isCheck).ToString();
}
Для случая, если понадобится выбрать все элементы — реализовал возможность выделения всех элементов
private void checkAll_Checked(object sender, RoutedEventArgs e)
{
foreach (Cust t in _collectionSource)
{
t.isCheck = true;
}
FilterItems(textBoxSearch.Text.ToLower());
textCol.Text = _collectionSource.Count.ToString();
}
private void checkAll_Unchecked(object sender, RoutedEventArgs e)
{
foreach (Cust t in _collectionSource)
{
t.isCheck = false;
}
FilterItems(textBoxSearch.Text.ToLower());
textCol.Text = "0";
}
Итого
В итоге — получился вот такой контролл, в котором быстро можно найти один или несколько элементов. При этом — подсвечивается найденный текст в элементе.
Спасибо за внимание. Надеюсь, статья была полезна и интересна.
P.S. исходники можно взять тут narod.ru либо dropbox