ReactiveUI: How to pass an 'async' parameter to ReactiveCommand.CreateFromTask() - c#

I have defined the ReactiveCommand like this:
CmdGetTestResult = ReactiveCommand.CreateFromTask<bool>(async detailed =>
{
var result = await Client.FooAsync(detailed);
});
CmdGetTestResult.ToPropertyEx(this, x => x.GetTestResultResponse);
viewmodel
public bool Detailed { get; set; }
public Unit GetTestResultResponse { [ObservableAsProperty]get; }
public ReactiveCommand<bool, Unit> CmdGetTestResult { get; }
In xaml:
<Button
Content="get test result (F4)"
Command="{Binding Path=ViewModel.CmdGetTestResult}"
CommandParameter="{Binding Path=ViewModel.Detailed}"/>
The property detailed is true (checked in debugger), but it is always passed as false. What's wrng with my code?
EDIT:
I have also tried the reactiveui way:
this.BindCommand(ViewModel, model => model.CmdGetTestResult, window => window.ButtonGetTestResult, model => model.Detailed);
without any success and same problem..

just been bumping my head and appears it's like that:
private readonly ObservableAsPropertyHelper<List<LocationData>> _locations;
public ReactiveCommand<LocationData, List<LocationData>> GetLocations { get; }
public ViewModel
{
GetLocations = ReactiveCommand.CreateFromTask<LocationData, List<LocationData>>(GetLocationsTask);
}
private async Task<List<LocationData>> GetLocationsTask(LocationData i)
{
return await LocationService.GetLocationsAsync(i);
}
this is just a tester...

Related

Xamarin Forms - Changes aren't detected

I've been trying to make a Custom search field that, on the fly, should add objects to a list when typing.
But for some reason it only shows the list with an item when i hot reload.
CreateHerdPageViewModel
public class CreateHerdPageViewModel : ViewModelBase
{
private IHerdService herdService;
private string searchInput;
public string SearchInput { get => searchInput;
set {
SetProperty(ref searchInput, value);
RaisePropertyChanged(nameof(HerdSearchResults));
}
}
private List<Herd> herdSearchResults;
public List<Herd> HerdSearchResults
{
get => herdSearchResults;
set {
SetProperty(ref herdSearchResults, value);
}
}
private List<Herd> allHerds;
public List<Herd> AllHerds { get => allHerds; set => SetProperty(ref allHerds, value); }
public DelegateCommand SearchChrOrAddressCommand { get; set; }
public CreateHerdPageViewModel(INavigationService navigationService, IHerdService herdService)
: base(navigationService)
{
this.herdService = herdService;
SearchChrOrAddressCommand = new DelegateCommand(SearchChrOrAddress);
}
private void SearchChrOrAddress()
{
Herd herdMatch = new Herd();
for (int i = 0; i < AllHerds.Count; i++)
{
herdMatch = AllHerds[i];
}
if (herdMatch.ChrAddress.Area.Contains(SearchInput))
{
if (HerdSearchResults.Contains(herdMatch) == false)
{
HerdSearchResults.Add(herdMatch);
RaisePropertyChanged(nameof(HerdSearchResults));
}
}
}
public async override void OnNavigatedTo(INavigationParameters parameters)
{
base.OnNavigatedTo(parameters);
AllHerds = await herdService.GetHerds();
HerdSearchResults = new List<Herd>();
}
}
}
CreateHerdPage.Xaml
xmlns:yummy="clr-namespace:Xamarin.Forms.PancakeView;assembly=Xamarin.Forms.PancakeView"
xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
xmlns:CustomRenderer="clr-namespace:ChrApp.CustomRenderer">
<yummy:PancakeView
Grid.Column="0"
Grid.Row="0"
Grid.ColumnSpan="3"
CornerRadius="10">
<CustomRenderer:NoUnderlineEntry
x:Name="SearchField"
Style="{StaticResource UpdateEntry}"
Margin="0"
TextChanged="RemovceSearchIcon"
Text="{Binding SearchInput}">
<CustomRenderer:NoUnderlineEntry.Behaviors>
<b:EventToCommandBehavior
EventName="TextChanged"
Command="{Binding SearchChrOrAddressCommand}"/>
</CustomRenderer:NoUnderlineEntry.Behaviors>
</CustomRenderer:NoUnderlineEntry>
</yummy:PancakeView>
As you can see, i've tried different approaches to make it recognize changes, but without luck.
Can someone enlighten me on what i'm missing?
Thanks in advance
So the issue was that I used an ordinary List, and not an ObservableCollection. So swithcing to this solved the issue.

How to change value in one class if value changes in another

Let's say I have a parameter in my ViewModel:
public string ChosenQualityParameter
{
get => DefectModel.SelectedQualDefectParameters?.Name ?? "Не выбран параметр";
}
and I have a class DefectModel with parameter SelectedQualDefectParameters.Name in it. I want to change the UI binded to ChosenQualityParameter, when theName parameter changes too.
But I don't know how to do this properly. Any suggestions? Thanks in advance.
You might define your ViewModel class like this:
public class ViewModel
{
private DefectModel _defectModel;
public ViewModel(DefectModel defectModel)
{
_defectModel = defectModel;
}
public string ChosenQualityParameter
{
get => _defectModel.SelectedQualDefectParameters?.Name ?? "Не выбран параметр";
}
}
I personally do not like such dependencies in viewmodels, but it might get the job done here. It seems to work in a console application anyway:
using System;
public class Parameters
{
public string Name { get; set; }
}
public class DefectModel
{
public Parameters SelectedQualDefectParameters { get; set; }
}
public class ViewModel
{
private DefectModel _defectModel;
public ViewModel(DefectModel defectModel)
{
_defectModel = defectModel;
}
public string ChosenQualityParameter
{
get => _defectModel.SelectedQualDefectParameters?.Name ?? "Не выбран параметр";
}
}
class Program
{
static void Main()
{
var defectModel = new DefectModel
{
SelectedQualDefectParameters = new Parameters
{
Name = "test"
}
};
var viewModel = new ViewModel(defectModel);
Console.WriteLine(viewModel.ChosenQualityParameter);
defectModel.SelectedQualDefectParameters.Name = "changed";
Console.WriteLine(viewModel.ChosenQualityParameter);
Console.ReadKey();
}
}
Thanks to #Knoop and #BartHofland, I've solved my issue by using INotifyPropertyChanged in my DefectModel and SelectedQualDefectParameters classes.
For setting ChosenQualityParameter I used MessagingCenter to send new value.

MvvmCross - Passing a string with IMvxNavigationService

I'm currently working on a Xamarin.iOS project that uses a web-api to gather data. However, I'm running into some problems trying to pass the user input from a textfield to the Tableview that gets the result from the api.
To do this I've followed the example on the MvvmCross documentation.
The problem is that the input from the Textfield never reaches the 'Filter' property in my TableviewController's viewmodel. I think I'm not passing the string object correctly to my IMvxNavigationService when called.
To clarify, in my UserinputViewController I'm binding the textfield's text like so:
[MvxFromStoryboard(StoryboardName = "Main")]
public partial class SearchEventView : MvxViewController
{
public SearchEventView (IntPtr handle) : base (handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel> set = new MvxFluentBindingDescriptionSet<SearchEventView, SearchEventViewModel>(this);
set.Bind(btnSearch).To(vm => vm.SearchEventCommand);
set.Bind(txtSearchFilter).For(s => s.Text).To(vm => vm.SearchFilter);
set.Apply();
}
}
The Viewmodel linked to this ViewController looks like this:
public class SearchEventViewModel : MvxViewModel
{
private readonly IMvxNavigationService _navigationService;
private string _searchFilter;
public string SearchFilter
{
get { return _searchFilter; }
set { _searchFilter = value; RaisePropertyChanged(() => SearchFilter); }
}
public SearchEventViewModel(IMvxNavigationService mvxNavigationService)
{
this._navigationService = mvxNavigationService;
}
public IMvxCommand SearchEventCommand {
get {
return new MvxCommand<string>(SearchEvent);
}
}
private async void SearchEvent(string filter)
{
await _navigationService.Navigate<EventListViewModel, string>(filter);
}
}
And finally, TableviewController's viewmodel looks like this:
public class EventListViewModel : MvxViewModel<string>
{
private readonly ITicketMasterService _ticketMasterService;
private readonly IMvxNavigationService _navigationService;
private List<Event> _events;
public List<Event> Events
{
get { return _events; }
set { _events = value; RaisePropertyChanged(() => Events); }
}
private string _filter;
public string Filter
{
get { return _filter; }
set { _filter = value; RaisePropertyChanged(() => Filter); }
}
public EventListViewModel(ITicketMasterService ticketMasterService, IMvxNavigationService mvxNavigationService)
{
this._ticketMasterService = ticketMasterService;
this._navigationService = mvxNavigationService;
}
public IMvxCommand EventDetailCommand {
get {
return new MvxCommand<Event>(EventDetail);
}
}
private void EventDetail(Event detailEvent)
{
_navigationService.Navigate<EventDetailViewModel, Event>(detailEvent);
}
public override void Prepare(string parameter)
{
this.Filter = parameter;
}
public override async Task Initialize()
{
await base.Initialize();
//Do heavy work and data loading here
this.Events = await _ticketMasterService.GetEvents(Filter);
}
}
Whenever trying to run, the string object 'parameter' in my TableviewController's Prepare function remains 'null' and I have no idea how to fix it. Any help is greatly appreciated!
I believe the issue is with your command setup
new MvxCommand<string>(SearchEvent);
As this command is being bound to a standard UIButton. It will not pass through a parameter value of your filter but null instead. So the string parameter generic can be removed. Additionally, as you want to execute an asynchronous method I would suggest rather using MvxAsyncCommand
new MvxAsyncCommand(SearchEvent);
Then in terms of SearchEvent method you can remove the parameter. The value of filter is bound to your SearchFilter property. It is this property's value that you want to send as the navigation parameter.
private async Task SearchEvent()
{
await _navigationService.Navigate<EventListViewModel, string>(SearchFilter);
}

How to get XAML Command to execute Async?

Assuming I have the following command
public class SignOutCommand : CommandBase, ISignOutCommand
{
private readonly UserModel _userModel;
private readonly IUserService _userService;
public SignOutCommand(IUserService userService)
{
_userService = userService;
_userModel = App.CurrentUser;
}
public bool CanExecute(object parameter)
{
var vm = parameter as EditProfileViewModel;
return vm != null;
}
public Task<bool> CanExecuteAsync(object parameter)
{
return Task.Run(() => CanExecute(parameter);
}
public void Execute(object parameter)
{
var vm = (EditProfileViewModel)parameter;
var signOutSucceeded = SignOutUser();
if (signOutSucceeded)
{
vm.AfterSuccessfulSignOut();
}
}
public Task ExecuteAsync(object parameter)
{
return Task.Run(() => Execute(parameter);
}
private bool SignOutUser()
{
// populate this so that we get the attached collections.
var user = _userService.GetByEmailAddress(_userModel.Email);
_userService.RevokeLoggedInUser(user);
return true;
}
}
And my XAML has a button
<Button Text="Sign out"
Command="{Binding SignOutCommand}"
CommandParameter="{Binding}" />
What would it take for this to execute the ExecuteAsync instead of Execute? Sorry if this is trivial, I'm quite new to XAML.
Also, I'm actually doing this in Xamarin.Forms XAML, not sure if it makes a difference here.
You could simply implement your command this way
public class SignOutCommand : ICommand
{
public bool CanExecute(object parameter)
{
var vm = parameter as EditProfileViewModel;
return vm != null;
}
public async void Execute(object parameter)
{
Task.Run(() =>
{
var vm = (EditProfileViewModel)parameter;
var signOutSucceeded = SignOutUser();
if (signOutSucceeded)
{
vm.AfterSuccessfulSignOut();
}
}
}
...
}
But then the bound button is not disabled during execution and the command can be executed even if it is already running...
If you need that the command cannot be executed during execution have a look at:
https://mytoolkit.codeplex.com/wikipage?title=AsyncRelayCommand
Source code: https://xp-dev.com/svn/mytoolkit/MyToolkit/Command/
Implementing the CanExecute asynchronously would be more tricky...

Wpf ICollectionView Binding item cannot resolve property of type object

I have bound a GridView with an ICollectionView in the XAML designer the properties are not known because the entity in the CollectionView have been transformed into type Object and the entity properties can't be accessed, it runs fine no error but the designer shows it as an error, if I bind to the collection I can access the properties fine
Example the entity is a Person with a string Name property I place them in an ObservableCollection<Person> and get the view from it and bind it to the GridView.ItemsSource now when I try to set the column header DataMemberBinding.FirstName property the designer shows it as an error
Cannot Resolve property 'FirstName' in data Context of type object
Is it a bug or is it Resharper playing tricks on me
Sample code:
public class Person
{
public string FirstName{
get { return _firstName; }
set { SetPropertyValue("FirstName", ref _firstName, value); }
}
}
public class DataService
{
public IDataSource DataContext { get; set; }
public ICollectionView PersonCollection{ get; set; }
public DataService()
{
DataContext = new DataSource();
//QueryableCollectionView is from Telerik
//but if i use any other CollectionView same thing
//DataContext Persons is an ObservableCollection<Person> Persons
PersonCollection = new QueryableCollectionView(DataContext.Persons);
}
}
<telerik:RadGridView x:Name="ParentGrid"
ItemsSource="{Binding DataService.PersonCollection}"
AutoGenerateColumns="False">
<telerik:RadGridView.Columns >
<telerik:GridViewDataColumn Header="{lex:Loc Key=FirstName}"
DataMemberBinding="{Binding FirstName}"/>
</telerik:RadGridView.Columns>
</telerik:RadGridView>
The warnings that Resharper is giving you in the XAML view is because the design-time view of the control does not know what type it's data-context is. You can use a d:DesignInstance to help with your bindings.
Add the following (replacing Assembly/Namespace/Binding Target names appropriately)
<UserControl x:Class="MyNamespace.UserControl1"
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"
mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:lcl="clr‐namespace:MyAssembly"
d:DataContext="{d:DesignInstance Type=lcl:ViewModel}">
Your entity has not been transformed in object, it's because the interface ICollectionView is not a generic collection so ReSharper has no way to know that it holds a collection of Person.
You can create a generic version of ICollectionView and use it for your PersonCollection property as demonstrated in this post https://benoitpatra.com/2014/10/12/a-generic-version-of-icollectionview-used-in-a-mvvm-searchable-list/.
First some interfaces:
public interface ICollectionView<T> : IEnumerable<T>, ICollectionView
{
}
public interface IEditableCollectionView<T> : IEditableCollectionView
{
}
The implementation:
public class GenericCollectionView<T> : ICollectionView<T>, IEditableCollectionView<T>
{
readonly ListCollectionView collectionView;
public CultureInfo Culture
{
get => collectionView.Culture;
set => collectionView.Culture = value;
}
public IEnumerable SourceCollection => collectionView.SourceCollection;
public Predicate<object> Filter
{
get => collectionView.Filter;
set => collectionView.Filter = value;
}
public bool CanFilter => collectionView.CanFilter;
public SortDescriptionCollection SortDescriptions => collectionView.SortDescriptions;
public bool CanSort => collectionView.CanSort;
public bool CanGroup => collectionView.CanGroup;
public ObservableCollection<GroupDescription> GroupDescriptions => collectionView.GroupDescriptions;
public ReadOnlyObservableCollection<object> Groups => collectionView.Groups;
public bool IsEmpty => collectionView.IsEmpty;
public object CurrentItem => collectionView.CurrentItem;
public int CurrentPosition => collectionView.CurrentPosition;
public bool IsCurrentAfterLast => collectionView.IsCurrentAfterLast;
public bool IsCurrentBeforeFirst => collectionView.IsCurrentBeforeFirst;
public NewItemPlaceholderPosition NewItemPlaceholderPosition
{
get => collectionView.NewItemPlaceholderPosition;
set => collectionView.NewItemPlaceholderPosition = value;
}
public bool CanAddNew => collectionView.CanAddNew;
public bool IsAddingNew => collectionView.IsAddingNew;
public object CurrentAddItem => collectionView.CurrentAddItem;
public bool CanRemove => collectionView.CanRemove;
public bool CanCancelEdit => collectionView.CanCancelEdit;
public bool IsEditingItem => collectionView.IsEditingItem;
public object CurrentEditItem => collectionView.CurrentEditItem;
public event NotifyCollectionChangedEventHandler CollectionChanged
{
add => ((ICollectionView) collectionView).CollectionChanged += value;
remove => ((ICollectionView) collectionView).CollectionChanged -= value;
}
public event CurrentChangingEventHandler CurrentChanging
{
add => ((ICollectionView) collectionView).CurrentChanging += value;
remove => ((ICollectionView) collectionView).CurrentChanging -= value;
}
public event EventHandler CurrentChanged
{
add => ((ICollectionView) collectionView).CurrentChanged += value;
remove => ((ICollectionView) collectionView).CurrentChanged -= value;
}
public GenericCollectionView([NotNull] ListCollectionView collectionView)
{
this.collectionView = collectionView ?? throw new ArgumentNullException(nameof(collectionView));
}
public IEnumerator<T> GetEnumerator()
{
return (IEnumerator<T>) ((ICollectionView) collectionView).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((ICollectionView) collectionView).GetEnumerator();
}
public bool Contains(object item)
{
return collectionView.Contains(item);
}
public void Refresh()
{
collectionView.Refresh();
}
public IDisposable DeferRefresh()
{
return collectionView.DeferRefresh();
}
public bool MoveCurrentToFirst()
{
return collectionView.MoveCurrentToFirst();
}
public bool MoveCurrentToLast()
{
return collectionView.MoveCurrentToLast();
}
public bool MoveCurrentToNext()
{
return collectionView.MoveCurrentToNext();
}
public bool MoveCurrentToPrevious()
{
return collectionView.MoveCurrentToPrevious();
}
public bool MoveCurrentTo(object item)
{
return collectionView.MoveCurrentTo(item);
}
public bool MoveCurrentToPosition(int position)
{
return collectionView.MoveCurrentToPosition(position);
}
public object AddNew()
{
return collectionView.AddNew();
}
public void CommitNew()
{
collectionView.CommitNew();
}
public void CancelNew()
{
collectionView.CancelNew();
}
public void RemoveAt(int index)
{
collectionView.RemoveAt(index);
}
public void Remove(object item)
{
collectionView.Remove(item);
}
public void EditItem(object item)
{
collectionView.EditItem(item);
}
public void CommitEdit()
{
collectionView.CommitEdit();
}
public void CancelEdit()
{
collectionView.CancelEdit();
}
}
And finally the usage:
ICollectionView<Person> PersonCollectionView { get; }
In the constructor:
var view = (ListCollectionView) CollectionViewSource.GetDefaultView(PersonCollection);
PersonCollectionView = new GenericCollectionView<Person>(view);
Neither
d:DataContext="{d:DesignInstance Type=lcl:ViewModel}">
nor
GenericCollectionView
works directly for a DataGrid with a CollectionViewSource.
<DataGrid AutoGenerateColumns="False"
ItemsSource="{Binding collectionViewSource.View}"
SelectedItem="{Binding SelectedRow}"
We can't set "d:DataContext"; because, we often need to bind multiple properties to our viewmodel.
The CollectionViewSource creates new ListCollectionView which is runtime instantiated each time you set the Source property. Since setting the Source property is the only reasonable way to refresh a range of rows, we can't keep a GenericCollectionView around.
My solution is perhaps perfectly obvious, but I dumped the CollectionViewSource. By making creating a property
private ObservableCollection<ListingGridRow> _rowDataStoreAsList;
public GenericCollectionView<ListingGridRow> TypedCollectionView
{
get => _typedCollectionView;
set { _typedCollectionView = value; OnPropertyChanged();}
}
public void FullRefresh()
{
var listData = _model.FetchListingGridRows(onlyListingId: -1);
_rowDataStoreAsList = new ObservableCollection<ListingGridRow>(listData);
var oldView = TypedCollectionView;
var saveSortDescriptions = oldView.SortDescriptions.ToArray();
var saveFilter = oldView.Filter;
TypedCollectionView = new GenericCollectionView<ListingGridRow>(new ListCollectionView(_rowDataStoreAsList));
var newView = TypedCollectionView;
foreach (var sortDescription in saveSortDescriptions)
{
newView.SortDescriptions.Add(new SortDescription()
{
Direction = sortDescription.Direction,
PropertyName = sortDescription.PropertyName
});
}
newView.Filter = saveFilter;
}
internal void EditItem(object arg)
{
var view = TypedCollectionView;
var saveCurrentPosition = view.CurrentPosition;
var originalRow = view.TypedCurrentItem;
if (originalRow == null)
return;
var listingId = originalRow.ListingId;
var rawListIndex = _rowDataStoreAsList.IndexOf(originalRow);
// ... ShowDialog ... DialogResult ...
var lstData = _model.FetchListingGridRows(listingId);
_rowDataStoreAsList[rawListIndex] = lstData[0];
view.MoveCurrentToPosition(saveCurrentPosition);
view.Refresh();
}
After adding
public T TypedCurrentItem => (T)collectionView.CurrentItem;
To the GenericCollectionView provided by Maxence.

Categories

Resources