About my Project: I want to Highlight over a Search function ListboxItems trough their Index.
Current Stage:
private void Menu_Search_Click(object sender
It can be done with Binding
, DataTrigger
and INotifyPropertyChanged
interface implementation (the power of WPF widely used in MVVM programming pattern).
I'll use MVVM approach for the example of what you asking for.
Example features
TextBox
and press Search
button, all items containing search text will be highlighted with yellow background.The following class needed to notifying UI about change in Property. I'll derive from in other classes and simply call OnPropertyChanged()
in property setters.
NotifyPropertyChanged.cs
public class NotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
In MVVM developers aren't using Click
Event because there's more powerful Command
exists. Next class implements ICommand
interface (grabbed from the documentation page linked above). It needed for easy commands implementation in the main code.
RelayCommand.cs
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
Person.cs
public class Person : NotifyPropertyChanged
{
private bool _highlighted;
public string Name { get; set; }
public bool Highlighted
{
get => _highlighted;
set
{
_highlighted = value;
OnPropertyChanged();
}
}
}
View Model in MVVM is class where View (UI/XAML) may set its DataContext
which required for Binding
.
MainViewModel.cs
public class MainViewModel : NotifyPropertyChanged
{
private ObservableCollection<Person> _persons;
private ICommand _searchCommand;
public ObservableCollection<Person> Persons
{
get => _persons;
set
{
_persons = value;
OnPropertyChanged();
}
}
// It's C# 8.0 but for earlier versions of C# use the following:
// _searchCommand ?? (_searchCommand = new ...
public ICommand SearchCommand => _searchCommand ??= new RelayCommand(parameter =>
{
if (parameter is string searchText)
{
foreach(Person person in Persons)
{
person.Highlighted = searchText.Length > 0 && person.Name.Contains(searchText, StringComparison.InvariantCultureIgnoreCase);
}
}
});
// }));
public MainViewModel()
{
// example data
Persons = new ObservableCollection<Person>
{
new Person { Name = "Alex" },
new Person { Name = "Jane" },
new Person { Name = "Nick" },
new Person { Name = "John" },
new Person { Name = "Brett" },
new Person { Name = "Peter" },
new Person { Name = "Mike" },
new Person { Name = "George" },
new Person { Name = "Anthony" }
};
}
}
I'm providing the full markup to make everything clear. Here's DataContext
setup and all the Controls.
MainWindow.xaml
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal">
<TextBox x:Name="SearchTextBox" Width="300" Margin="5"/>
<Button Content="Search" Margin="5" Command="{Binding SearchCommand}" CommandParameter="{Binding Text,ElementName=SearchTextBox}"/>
</StackPanel>
<ListBox Margin="5" Grid.Row="1" ItemsSource="{Binding Persons}">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Style.Triggers>
<DataTrigger Binding="{Binding Highlighted}" Value="True">
<Setter Property="Background" Value="Yellow"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
</Window>
That's it.
P.S. Code-behind class :)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
In case i was wrong with understanding the question and you need an automatic selection instead of highlight, the code may be changed in the following way:
Person.cs
public class Person : NotifyPropertyChanged
{
private bool _selected;
public string Name { get; set; }
public bool Selected
{
get => _selected;
set
{
_selected = value;
OnPropertyChanged();
}
}
}
MainViewModel.cs
public ICommand SearchCommand => _searchCommand ??= new RelayCommand(parameter =>
{
if (parameter is string searchText)
{
foreach(Person person in Persons)
{
person.Selected = searchText.Length > 0 && person.Name.Contains(searchText, StringComparison.InvariantCultureIgnoreCase);
}
}
});
MainWindow.xaml
<ListBox Margin="5" Grid.Row="1" ItemsSource="{Binding Persons}" SelectionMode="Extended">
<ListBox.Resources>
<Style TargetType="ListBoxItem">
<Setter Property="IsSelected" Value="{Binding Selected}"/>
</Style>
</ListBox.Resources>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>