Keyboard freezes on Xamarin.Forms Searchbar - c#

I have a custom implemented control which is very memory intensive (it basically displays a group header and an accordionControl for each group).
The CustomControls AccordionControlGrouping and AccordionControl both inherit from StackLayout, so I have implemented my own bindable ItemsSource. I actively change the views of the controls when the ItemsSource changes.
I have optimized this change now (using LINQ and BinarySearch and that stuff) to get from 5sec. per search to 0.8sec.
This wouldn't be a problem per se, but each time a user enters a key the keyboard freezes and is therefore very unconvinient to search.
My SearchBar is bound to the command of a ViewModel like this
<SearchBar x:Name="EventSearchBar"
Text="{Binding SearchText}"
SearchCommand="{Binding SearchCommand}"
Placeholder="Suche"/>
SearchCommand
private Xamarin.Forms.Command _searchCommand;
public System.Windows.Input.ICommand SearchCommand
{
get
{
_searchCommand = _searchCommand ?? new Xamarin.Forms.Command(UpdateAccordionControlGrouping, CanExecuteSearchCommand);
return _searchCommand;
}
}
private bool CanExecuteSearchCommand()
{
return true;
}
SearchText Property
private string _searchText = string.Empty;
public string SearchText
{
get { return _searchText; }
set
{
if (_searchText != value)
{
_searchText = value ?? string.Empty;
RaisePropertyChanged(nameof(SearchText));
// Perform the search
if (SearchCommand.CanExecute(null))
SearchCommand.Execute(null);
}
}
}
UpdateAccordionControlGrouping
private void UpdateAccordionControlGrouping()
{
// Do some LINQ stuff to group and filter a global collection of data.
// And raise the #OnPropertyChanged event for the ItemsSource of my
// custom control.
}
Now how do I make this whole process async?
I have already looked into this article and it didn't work for me.

To run your method 'UpdateAccordionControlGrouping()' async you can do this:
private async Task UpdateAccordionControlGrouping()
{
var newgrouping = await Task.Run(() =>
{
//do CPU bound work here
return newgrouping;
});
// assign newgrouping to the correct control
}
Be aware that if you need to change any data from the view (Bindings etc) you might get a CrossThreadException. You need to use a Dispatcher then as seen here: Dispatcher.Dispatch on the UI thread
If you work with MvvmLight you get to use the DispatchHelper Simple example of DispatcherHelper

Related

Xamarin.Forms and Prism - How to pass data and navigate to another view?

This's my first question here, so hi everybody.
I'm working on the mobile app in Xamarin.Forms with Prism. I've created ListView where shown data from the database.
When the user clicks in the selected row app should navigate to a new view and pass the selected item from ListView.
<ListView x:Name="DefectsBase"
RowHeight="65"
ItemsSource="{Binding Defects}"
ItemSelected="ShowDetailsEvent"
IsPullToRefreshEnabled="true"
RefreshCommand="{Binding Refresh}"
IsRefreshing="{Binding IsRefreshing}">
Code backend:
async void ShowDetailsEvent(object sender, EventArgs e)
{
var myListView = (ListView)sender;
var myItem = myListView.SelectedItem;
var p = new NavigationParameters();
p.Add("selectedDefect", myItem);
await _navigationService.NavigateAsync("DefectDetailsView", p);
}
Unfortunately, the app doesn't respond to pressing the selected row in ListView.
As I can see you are already using Prism and you have a List page with Items and you want to navigate to some details page based on the selected/taped/chosen item which the user taps in the ListView.
The idea is to move as much code and logic as we can to the view model and keep our code-behind. This is pretty easy to solve using Prism and EventToCommand behaviour.
In the example and answer below, I will show you how to solve this with few lines of code, with a nice code approach.
First of all, I recommend you use EventToCommand behaviour, you can include it with prism xmlns, like this: xmlns:prism="http://prismlibrary.com", later on, you can use it with ListView.
Remove ItemSelected event from your ListView and move the markup about it to the <ListView.Behaviors> part. Here is my code sample for the ListView which binds to some ObserverableCollection of the Car models:
<ListView ItemsSource="{Binding Cars}">
<ListView.ItemTemplate>
<DataTemplate>
...
</DataTemplate>
</ListView.ItemTemplate>
<ListView.Behaviors>
<prism:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding SelectedCarCommand}"
EventArgsParameterPath="Item" />
</ListView.Behaviors>
The main part here is <ListView.Behaviors>, where you can see that I am binding to the SelectedCarCommand which will be invoked when the user taps on some of the items from the list. I am using the ItemTapped event for this and passing the current "taped" item from the list as a parameter.
In order to follow this XAML part in my view model of this page, I have declared the DelegateCommand and method which will be called when the command is invoked. The view model part looks like this:
This is my CarListPageViewModel, take a look at DelegateCommand and SelectedCar method.
public class CarListPageViewModel
{
private readonly INavigationService _navigationService;
public ObservableCollection<Car> Cars { get; set; }
public DelegateCommand<Car> SelectedCarCommand { get; private set; }
public CarListPageViewModel(INavigationService navigationService, IDataProvider dataProvider)
{
_navigationService = navigationService;
// Insert test data into collection of Cars
Cars = new ObservableCollection<Car>(dataProvider.GetData());
SelectedCarCommand = new DelegateCommand<Car>(SelectedCar);
}
private async void SelectedCar(Car selectedCar)
{
NavigationParameters navigationParameters = new NavigationParameters
{
{ "selectedCar", selectedCar }
};
await _navigationService.NavigateAsync(nameof(CarDetailsPage), navigationParameters);
}
}
As you can see we have DelegateCommand defined with the type of parameter which will be passed, in my case, this is the Car class, the same class as our items in the ListView.
In the constructor, I did my initialization and defined the method which will be called, that method has a parameter of the type Car.
When the user taps on one of the items in the ListView, SelectedCar (method) will be called and we can pass the data to the next view using NavigationParameters and NavigationService.
In order to retrieve the passed data we can use INavigationAware in the details view model and with the OnNavigatedTo method, access the data which is being passed.
This is my CarDetailsPageViewModel, take a look at OnNavigatedTo method.
public class CarDetailsPageViewModel : BindableBase, INavigationAware
{
private string carTitle;
public string CarTitle
{
get { return carTitle; }
set { SetProperty(ref carTitle, value); }
}
private string photoUrl;
public string PhotoUrl
{
get { return photoUrl; }
set { SetProperty(ref photoUrl, value); }
}
public CarDetailsPageViewModel() { }
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("selectedCar"))
{
Car car = parameters.GetValue<Car>("selectedCar");
if (car != null)
{
CarTitle = $"{car.Make} {car.Model}";
PhotoUrl = car.PhotoUrl;
}
}
}
public void OnNavigatedFrom(INavigationParameters parameters) { }
}
From this answer and example, you can see:
How to, use EventToCommand behaviour with ListView
Define and use DelegateCommand with passing parameter
How to navigate to another view and pass navigation parameter and
... finally how to access the passed data.
Code and this sample you can find on my GitHub profile here.
Hope this answer was helpful for you!
Wishing you lots of luck with coding! 👋

ReactiveUI 6 instant search (MVVM)

I would like to react to user's typing within a text box.
Actually my question is similar to what has already been posted at Reactive Extensions Instant Search for WPF/MVVM.
Now that we're at the release 6 of ReactiveUI, the previous code is out of date. How can I implement it (with MVVM, i.e. not using events)?
The compelling example # http://reactiveui.net/ should help alot. You also want to make sure your xaml TextBox uses the UpdateSourceTrigger
<TextBox Text="{Binding SearchQuery, UpdateSourceTrigger=PropertyChanged}" Width="50" />
Code from RXUI copied here:
public class SearchViewModel : ReactiveObject, IRoutableViewHost
{
public ReactiveList<SearchResults> SearchResults { get; set; }
private string searchQuery;
public string SearchQuery {
get { return searchQuery; }
set { this.RaiseAndSetIfChanged(ref searchQuery, value); }
}
public ReactiveCommand<List<SearchResults>> Search { get; set; }
public ISearchService SearchService { get; set; }
}
and the constructor code
// Constructor
public SearchViewModel(ISearchService searchService = null)
{
SearchService = searchService ?? Locator.Current.GetService<ISearchService>();
// Here we're describing here, in a *declarative way*, the conditions in
// which the Search command is enabled. Now our Command IsEnabled is
// perfectly efficient, because we're only updating the UI in the scenario
// when it should change.
var canSearch = this.WhenAny(x => x.SearchQuery, x => !String.IsNullOrWhiteSpace(x.Value));
// ReactiveCommand has built-in support for background operations and
// guarantees that this block will only run exactly once at a time, and
// that the CanExecute will auto-disable and that property IsExecuting will
// be set according whilst it is running.
Search = ReactiveCommand.CreateAsyncTask(canSearch, async _ => {
return await searchService.Search(this.SearchQuery);
});
// ReactiveCommands are themselves IObservables, whose value are the results
// from the async method, guaranteed to arrive on the UI thread. We're going
// to take the list of search results that the background operation loaded,
// and them into our SearchResults.
Search.Subscribe(results => {
SearchResults.Clear();
SearchResults.AddRange(results);
});
// ThrownExceptions is any exception thrown from the CreateAsyncTask piped
// to this Observable. Subscribing to this allows you to handle errors on
// the UI thread.
Search.ThrownExceptions
.Subscribe(ex => {
UserError.Throw("Potential Network Connectivity Error", ex);
});
// Whenever the Search query changes, we're going to wait for one second
// of "dead airtime", then automatically invoke the subscribe command.
this.WhenAnyValue(x => x.SearchQuery)
.Throttle(TimeSpan.FromSeconds(1), RxApp.MainThreadScheduler)
.InvokeCommand(this, x => x.Search);
}

PropertyChangedEvent and CanExecute issue

I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}
I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.
I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl

Composite Binding to property and inner viewmodel property not firing

I've created a property "IsLoading" for my main view model. The idea is that a progressbar is displayed whenever this property is set to true. So far so good
The catch is, that I have a command, that calls another viewmodel (the code is there because it's a functionality from another page, but I want to be able to shortcut it as well from my main viewmodel)
So, I went ahead and modified the main property to something like this :
public const string IsLoadingPropertyName = "IsLoading";
private bool _isLoading;
public bool IsLoading
{
get
{
return _isLoading || ((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.IsLoading;
}
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
}
}
}
and the xaml
<shell:SystemTray.ProgressIndicator>
<shell:ProgressIndicator IsIndeterminate="true" IsVisible="{Binding Main.IsLoading, Source={StaticResource Locator}}" />
</shell:SystemTray.ProgressIndicator>
So, I'm saying that main view model is loading when there's something loading there, or if the settings view model is loading.
The problem is that the binding only works when setting the main view model's IsLoading property, it doesn't react when I set it in the inner IsLoading one. Both have the same property name "IsLoading". Shouldn't it be detected?
For example, in Main view model (just the execution of the command for simplicity) :
private void ExecuteRefreshCommand()
{
ViewModelLocator viewModelLocator = Application.Current.Resources["Locator"] as ViewModelLocator;
viewModelLocator.SettingsViewModel.GetCurrentLocationCommand.Execute(null);
}
and inside the settings view model :
public RelayCommand GetCurrentLocationCommand
{
get
{
Action getLocation = () =>
{
if (!NetworkInterface.GetIsNetworkAvailable())
{
return;
}
var watcher = new GeoCoordinateWatcher(GeoPositionAccuracy.Default);
watcher.PositionChanged += WatcherPositionChanged;
IsLoading = true; // settings view model "IsLoading" propertychanged raising property
watcher.Start();
};
return new RelayCommand(getLocation);
}
}
You're looking at the MainViewModel's isLoading property to determine whether to show the progressbar or not. Silverlight uses the NotifyPropertyChanged event to determine when it should reevaluate a certain property. When setting either the SettingsViewModel's IsLoading property- or the MainViewModel's property, you only raise the changedEvent for that ViewModel. You should raise the ChangedEvent for both.
A modified setter example could be (depending on the exposed methods)
set
{
if (value != _isLoading)
{
_isLoading = value;
RaisePropertyChanged(IsLoadingPropertyName);
((ViewModelLocator)Application.Current.Resources["Locator"]).SettingsViewModel.RaisePropertyChanged(IsLoadingPropertyName);
}
}
Note that many MVVM frameworks offer a functionality called Messaging which is ideal to do cross ViewModel communication without creating the strict dependency you created right now. Alternatively you can use a globally consumed IsLoading property.

MVVM Implementation for WinForm application

I am trying to implement MVVM (Model View ViewModel) pattern for my WinForms application. I am using C# 2005.
My application has a MainForm (View) with 2 multi line textboxes and 3 buttons. The purpose of the 1st textbox is to show a running commentary of what the application is doing, when the button is clicked. I keep on appending lines to the TextBox to update the user what is happening. The purpose of the 2nd textbox is to update the user about any error condition, conflicts, duplicate values; in short, anything which is required by the user to review. It classifies each message as either an INFO or a WARNING or an ERROR. Each of the 3 buttons perform an action, and keeps updating the 2 textboxes.
I have created a MainFormViewModel class.
1st question:
When the user clicks on the button in MainForm, I have to clear the contents of the 2 textboxes, and disable the button so that it cant be clicked again until 1st operation is completed. Should I do this textbox and button updation directly in the MainForm or I should use MainFormViewModel in some way?
2nd question:
The button click calls a method on the MainFormViewModel class. Before calling the method and after calling the method, I want to show a message in the 1st textbox something like "Operation A started / ended". I do this by calling a Common class which has a Log method to log messages to a TextBox or a file or both. Again whether it is ok to do this directly from the MainForm? I call this logging method at the start and end of the event handler.
3rd question:
How do I propagate error messages from ViewModel back to View? I have created a custom Exception class "TbtException". So do I have to write 2 catch blocks in each and every button, one for TbtException and other for genetic Exception class?
Thanks.
You should perform operations in the view only with regard to the state of the ViewModel object. E.g. you shouldn't assume the view model is calculating when you click a button, but you should add a state to the view model that says it's doing something longer and then recognize that state in the view. You shouldn't disable or enable buttons in the view as you please, but only if there's a state that demands these buttons to be changed. This can go as far as to have a property that indicates which item in a list is currently selected, so the UI doesn't call the list control's SelectedItem member, but the viewmodel's. And when the user clicks remove, then the view model will remove the selected member from its list and the view is automatically updated through state changes in the form of events.
Here's what I would call a view model for your view. It exposes messages through an observable collection to which the view can bind (ie. register event handlers, since binding is not well supported in WinForms). The textbox at any time renders only the contents of the collection. It has actions for clearing those collections which your view can call. The view can also call actions of the underlying model, but it should be updated through the viewmodel only! The view should never register any event handlers for events exposed by the underlying model. If you ever want to do that you should hook up that event in the view model and expose it to the view there. Sometimes that may feel like "just another level of indirection" which is why it may be overkill for very simple applications such as yours.
public class MainFormViewModel : INotifyPropertyChanged {
private object syncObject = new object();
private MainFormModel model;
public virtual MainFormModel Model {
get { return model; }
set {
bool changed = (model != value);
if (changed && model != null) DeregisterModelEvents();
model = value;
if (changed) {
OnPropertyChanged("Model");
if (model != null) RegisterModelEvents();
}
}
}
private bool isCalculating;
public bool IsCalculating {
get { return isCalculating; }
protected set {
bool changed = (isCalculating != value);
isCalculating = value;
if (changed) OnPropertyChanged("IsCalculating");
}
}
public ObservableCollection<string> Messages { get; private set; }
public ObservableCollection<Exception> Exceptions { get; private set; }
protected MainFormViewModel() {
this.Messages = new ObservableCollection<string>();
this.Exceptions = new ObservableCollection<string>();
}
public MainFormViewModel(MainFormModel model)
: this() {
Model = model;
}
protected virtual void RegisterModelEvents() {
Model.NewMessage += new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown += new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void DeregisterModelEvents() {
Model.NewMessage -= new EventHandler<SomeEventArg>(Model_NewMessage);
Model.ExceptionThrown -= new EventHandler<OtherEventArg>(Model_ExceptionThrown);
}
protected virtual void Model_NewMessage(object sender, SomeEventArg e) {
Messages.Add(e.Message);
}
protected virtual void Model_ExceptionThrown(object sender, OtherEventArg e) {
Exceptions.Add(e.Exception);
}
public virtual void ClearMessages() {
lock (syncObject) {
IsCalculating = true;
try {
Messages.Clear();
} finally { IsCalculating = false; }
}
}
public virtual void ClearExceptions() {
lock (syncObject) {
IsCalculating = true;
try {
Exceptions.Clear();
} finally { IsCalculating = false; }
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropetyChanged(string property) {
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(property));
}
}
EDIT: Regarding exception handling
I would rather catch exceptions in the ViewModel than in the view. The view model is better suited to prepare them for display. I don't know how that works in WPF though. I've yet to program an application in WPF, we're doing a lot of WinForms still.
Opinions may vary, but I think generic try/catch clauses aren't really exception handling. I think you should rather test your UI very well and include exception handling only when necessary. Which is why you unit test your view model and user test the view. However if you really stick to the principle and avoid logic in the view, you can do a lot with unit tests.

Categories