Что у меня есть
У меня есть UserControl
из TextBox
и ListBox
. ListBox
имеет ItemsSource
, связанное с ObservableCollection
в DataContext
через ListCollectionView
с пользовательской сортировкой и фильтром, как мы увидим ниже. Целью элемента управления является отображение в ListBox
только тех элементов (string
s) в исходной коллекции, которые содержат текст в TextBox
. Для этого я применяю фильтр к ListCollectionView
.
У меня есть два дополнительных ограничения. 1 моя исходная коллекция не отсортирована по алфавиту, но элементы, отображаемые в ListBox
, используют ListCollectionView
CustomSort
. 2, я должен отображать только первые 5 элементов (отсортированных по алфавиту), которые соответствуют строке в TextBox
. Для этого я применяю фильтр к ListCollectionView
.
Ожидание
Допустим, моя коллекция определена как таковая в моем DataContext
:
this.AllItems = new ObservableCollection<string>
{
"Banana",
"Watermelon",
"Peach",
"Grape",
"Apple",
"Pineapple",
"Cherry",
"Durian",
"Rambutan",
"Strawberry",
"Raspberry",
"Lemon",
"Orange",
"Sugar cane",
"Guava",
"Tomato",
"Coconut",
"Melon",
"Äpple",
"Glaçon",
"Etape",
"Étape"
};
И в моем TextBox
я ввожу букву «e» (все сравнения выполняются без учета регистра). Я ожидаю, что ListBox
отобразит следующие 5 элементов (для CurrentUICulture
установлено значение fr-FR):
- яблоко
- Яблоко
- вишня
- Этап
- Этап
потому что это первые 5 элементов, которые содержат букву «е» при сортировке по алфавиту. Однако я получаю следующие элементы в своем приложении:
- яблоко
- Виноград
- Персик
- Ананас
- Арбуз
потому что это первые 5 предметов в моей коллекции, которые содержат букву "e" THEN, отсортированные по алфавиту.
Мой код
Вот код, чтобы понять, что у меня есть, и мою проблему. Это должно работать практически только с использованием копирования/вставки ниже (остерегайтесь пространств имен и CurrentUICulture
). Я использую С# 4.0.
1) MainWindow
<Window x:Class="MyNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525"
xmlns:local="clr-namespace:MyNamespace">
<Window.Resources>
<local:FoobarViewModel x:Key="Foobar"/>
</Window.Resources>
<StackPanel>
<local:Foobar DataContext="{StaticResource Foobar}" AllItems="{Binding AllItems}"/>
</StackPanel>
</Window>
2) Класс, используемый как DataContext
public class FoobarViewModel : INotifyPropertyChanged
{
private ObservableCollection<string> allItems;
public event PropertyChangedEventHandler PropertyChanged;
public FoobarViewModel()
{
this.AllItems = new ObservableCollection<string>
{
"Banana",
"Watermelon",
"Peach",
"Grape",
"Apple",
"Pineapple",
"Cherry",
"Durian",
"Rambutan",
"Strawberry",
"Raspberry",
"Lemon",
"Orange",
"Sugar cane",
"Guava",
"Tomato",
"Coconut",
"Melon",
"Äpple",
"Glaçon",
"Etape",
"Étape"
};
}
public ObservableCollection<string> AllItems
{
get
{
return this.allItems;
}
set
{
this.allItems = value;
this.OnPropertyChanged("AllItems");
}
}
private void OnPropertyChanged(string propertyName)
{
var handler = this.PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
3) XAML моего UserControl
<UserControl x:Class="MyNamespace.Foobar"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBox x:Name="textbox" Grid.Row="0"/>
<ListBox x:Name="listbox" Grid.Row="1"/>
</Grid>
</UserControl>
4) И, наконец, самый важный код моего UserControl
Foobar
.
public partial class Foobar : UserControl
{
#region Fields
public static readonly DependencyProperty AllItemsProperty = DependencyProperty.Register(
"AllItems",
typeof(IEnumerable<string>),
typeof(Foobar),
new PropertyMetadata(AllItemsChangedCallback));
private const int MaxItems = 5;
#endregion
#region Constructors
public Foobar()
{
InitializeComponent();
textbox.KeyUp += TextboxKeyUp;
}
#endregion
#region Properties
public IEnumerable<string> AllItems
{
get { return (IEnumerable<string>)this.GetValue(AllItemsProperty); }
set { this.SetValue(AllItemsProperty, value); }
}
#endregion
#region Methods
private void TextboxKeyUp(object sender, KeyEventArgs e)
{
TextBox localTextBox = sender as TextBox;
if (localTextBox != null)
{
var items = ((ListCollectionView)listbox.ItemsSource).SourceCollection;
if (items.Cast<string>().Any(x => x.ToLower(CultureInfo.CurrentUICulture).Contains(localTextBox.Text.ToLower(CultureInfo.CurrentUICulture))))
{
this.ApplyFilter();
listbox.Visibility = Visibility.Visible;
}
else
{
listbox.Visibility = Visibility.Collapsed;
}
}
}
private static void AllItemsChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
Foobar control = sender as Foobar;
if (control != null)
{
List<string> source = new List<string>((IEnumerable<string>)e.NewValue);
ListCollectionView view = (ListCollectionView)CollectionViewSource.GetDefaultView(source);
view.CustomSort = new CustomSort();
control.listbox.ItemsSource = view;
control.ApplyFilter();
}
}
private void ApplyFilter()
{
ListCollectionView view = (ListCollectionView)listbox.ItemsSource;
int index = 0;
view.Filter = x =>
{
bool result = x.ToString().ToLower(CultureInfo.CurrentUICulture).Contains(textbox.Text.ToLower(CultureInfo.CurrentUICulture));
if (result)
{
index++;
}
return index <= MaxItems && result;
};
}
#endregion
private class CustomSort : IComparer
{
public int Compare(object x, object y)
{
return String.Compare(x.ToString(), y.ToString(), CultureInfo.CurrentUICulture, CompareOptions.IgnoreCase);
}
}
}
Весь код работает как положено, за исключением фильтрации, которая выполняется в методе ApplyFilter
. По сути, этот метод просто проверяет каждый элемент в коллекции на соответствие тому, что находится в TextBox
, и при условии, что число возвращенных элементов не превышает максимально допустимого, элемент будет включен в фильтр. Когда я отлаживаю этот метод, я вижу, что элементы просматриваются в исходном порядке коллекции, а не в порядке сортировки, хотя фильтр, похоже, выполняется на ListCollectionView
, а не на ObservableCollection<string>
.
Кажется, сначала применяется фильтр, а затем сортировка. Я хочу, чтобы сначала применялась сортировка, а затем фильтрация.
Мой вопрос
Как я могу применить фильтр к отсортированному ListCollectionView
, а не к исходной несортированной коллекции?