I've created a search page in my app and I'd like to be able to search through my ObservableCollection
of items in the ViewModel and display them onto a CollectionView
. So far this is what I've done and I get an exception i.e System.Reflection.TargetInvocationException: 'Exception has been thrown by the target of an invocation.'
every time I run the app.
SearchPage XAML
<!--Doctors Search Result-->
<Grid Grid.Row="1">
<CollectionView ItemsSource="{Binding RecentDoctors}">
<CollectionView.ItemsLayout>
<ListItemsLayout Orientation="Vertical" ItemSpacing="15"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate>
<StackLayout Orientation="Horizontal">
<!--Image-->
<Frame BackgroundColor="Black"
HeightRequest="20"
WidthRequest="20"
CornerRadius="100"
Margin="20,0,0,0"
HorizontalOptions="Start"
VerticalOptions="Center"
IsClippedToBounds="True">
<Image HorizontalOptions="Center"
VerticalOptions="Center"/>
</Frame>
<StackLayout Orientation="Vertical"
VerticalOptions="Center"
Spacing="-3">
<!--Fullname-->
<Label Text="{Binding DoctorsName}"
FontSize="19"
FontAttributes="Bold"/>
<!--Specialization-->
<Label Text="{Binding Specialization}"
FontSize="14"
TextColor="LightGray"/>
</StackLayout>
</StackLayout>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Grid>
<!--Doctors Search Bar-->
<Grid Grid.Row="0" ColumnSpacing="0" RowSpacing="0">
<pancake:PancakeView BackgroundColor="#0F8DF4"
HasShadow="True">
<Grid>
<!--The SearchBar-->
<renderers:CustomSearchBar x:Name="doctorsSearchBar"
Placeholder="Search Doctors by Name, Specialization"
VerticalOptions="Center"
FontSize="17"
TextColor="Black"
WidthRequest="320"
Text="{Binding SearchedText}"
SearchCommand="{Binding SearchBarCommand}"
SearchCommandParameter="{Binding Text, Source={x:Reference doctorsSearchBar}}"/>
</Grid>
</pancake:PancakeView>
</Grid>
SearchPage ViewModel
public class TelemedSearchPageViewModel : BaseViewModel
{
private string _searchedText;
public string SearchedText
{
get { return _searchedText; }
set
{
_searchedText = value;
OnPropertyChanged();
Search();
}
}
public ObservableCollection<RecentDoctorsInfo> RecentDoctors { get; set; } = new ObservableCollection<RecentDoctorsInfo>();
public ICommand SearchBarCommand { get; set; }
/// <summary>
/// Main Constructor
/// </summary>
public TelemedSearchPageViewModel()
{
SearchBarCommand = new RelayCommand(Search);
//RecentDoctorsList
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Steven Strange",
Specialization = "Sorcerer Supreme",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Peter Parker",
Specialization = "Spiderman",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Bruce Banner",
Specialization = "The Hulk",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Reed Richards",
Specialization = "Mr.Fantastic",
Location = "177a Bleecker St. | USA"
});
}
#region METHODS
public void Search()
{
if (RecentDoctors != null && RecentDoctors.Count >0)
{
var temp = RecentDoctors.Where(x => x.DoctorsName.ToLower().Contains(SearchedText.ToLower()));
foreach (var item in temp)
{
RecentDoctors.Add(item);
}
}
}
#endregion
}
Edit3:
if (RecentDoctors != null && RecentDoctors.Count > 0)
{
var results = RecentDoctors.Where(x => x.DoctorsName.ToLower().Contains(SearchedText.ToLower()));
SearchResults.Clear();
foreach (RecentDoctorsInfo item in results)
{
SearchResults.Add(item);
}
}
else
{
RecentDoctors.Clear();
}
If you want to execute the search when user type you should use a behavior as doscs suggest
public class SearchBarTextChangedBehavior : Behavior<SearchBar>
{
protected override void OnAttachedTo(SearchBar bindable)
{
base.OnAttachedTo(bindable);
bindable.TextChanged += this.SearchBar_TextChanged;
}
protected override void OnDetachingFrom(SearchBar bindable)
{
base.OnDetachingFrom(bindable);
bindable.TextChanged -= this.SearchBar_TextChanged;
}
private void SearchBar_TextChanged(object sender, TextChangedEventArgs e)
{
((SearchBar)sender).SearchBarCommand?.Execute(e.NewTextValue);
}
}
Then attach the behavior to your SearchBar
<renderers:CustomSearchBar
x:Name="doctorsSearchBar"
Placeholder="Search Doctors by Name, Specialization"
VerticalOptions="Center"
FontSize="17"
TextColor="Black"
WidthRequest="320"
Text="{Binding SearchedText}"
SearchCommand="{Binding SearchBarCommand}">
<renderers:CustomSearchBar.Behaviors>
<behaviors:SearchBarTextChangedBehavior />
</renderers:CustomSearchBar.Behaviors>
</renderers:CustomSearchBar>
By the other hand, you should create a private copy of the original list and add the same items as the public collection
private List<RecentDoctorsInfo> originalRecentDoctorsList = new List<RecentDoctorsInfo>();
public ObservableCollection<RecentDoctorsInfo> RecentDoctors { get; set; } = new ObservableCollection<RecentDoctorsInfo>();
public ICommand SearchBarCommand { get; set; }
public TelemedSearchPageViewModel()
{
SearchBarCommand = new RelayCommand(Search);
//RecentDoctorsList
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Steven Strange",
Specialization = "Sorcerer Supreme",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Peter Parker",
Specialization = "Spiderman",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Bruce Banner",
Specialization = "The Hulk",
Location = "177a Bleecker St. | USA"
});
RecentDoctors.Add(new RecentDoctorsInfo()
{
DoctorsName = "Reed Richards",
Specialization = "Mr.Fantastic",
Location = "177a Bleecker St. | USA"
});
// Backup copy list.
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Steven Strange",
Specialization = "Sorcerer Supreme",
Location = "177a Bleecker St. | USA"
});
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Peter Parker",
Specialization = "Spiderman",
Location = "177a Bleecker St. | USA"
});
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Bruce Banner",
Specialization = "The Hulk",
Location = "177a Bleecker St. | USA"
});
originalRecentDoctorsList.Add(new RecentDoctorsInfo()
{
DoctorsName = "Reed Richards",
Specialization = "Mr.Fantastic",
Location = "177a Bleecker St. | USA"
});
}
And by last, your Search
method should clean the public collection (the one you're showing) and use the private as backup
private void Search()
{
if (!string.IsNullOrEmpty(SearchedText))
{
var filteredDoctors = RecentDoctors
.Where(x =>
x.DoctorsName.ToLower().Contains(SearchedText.ToLower()))
.ToList();
RecentDoctors.Clear();
foreach(var recentDoctor in filteredDoctors)
RecentDoctors.Add(recentDoctor);
}
else
{
// This is when you clean the text from the search
RecentDoctors.Clear();
foreach(var originalRecentDoctor in originalRecentDoctorsList)
RecentDoctors.Add(originalRecentDoctor);
}
}