How can I access a property of a page in a ViewModel? - xamarin

I have this code that creates a ViewModel for the page. However in the Viewmodel I want to access the property correctButtonPressed but it's not available.
How can I access this?
public partial class PhrasesFrame : Frame
{
public int correctButtonPressed;
public PhrasesFrameViewModel vm = new PhrasesFrameViewModel();
public PhrasesFrame() {
InitializeComponent();
}
private string abc()
{
var a = correctButtonPressed;
}
}
public class PhrasesFrameViewModel : ObservableProperty
{
private ICommand bButtonClickedCommand;
public ICommand BButtonClickedCommand
{
get
{
return bButtonClickedCommand ??
(bButtonClickedCommand = new Command(() =>
{
// Next line gives an error "use expression body for accessors" "use expression body for properties.
correctButtonPressed = 123;
}));
}
}
}

Expose it as a property on the view model and have the view access it.
public partial class PhrasesFrame : Frame {
public PhrasesFrameViewModel vm = new PhrasesFrameViewModel();
public PhrasesFrame() {
InitializeComponent();
}
private string abc() {
//View can access correctButtonPressed via the view model.
var a = vm.correctButtonPressed;
}
}
public class PhrasesFrameViewModel : ObservableProperty {
public int correctButtonPressed;
private ICommand bButtonClickedCommand;
public ICommand BButtonClickedCommand {
get {
return bButtonClickedCommand ??
(bButtonClickedCommand = new Command(() => {
correctButtonPressed = 123;
}));
}
}
}

Related

In a class inheriting from another, how can I set a value in the constructor of the base class?

I have this code:
using System;
using Xamarin.Forms;
namespace Test
{
public partial class ScrollHeadingView : ContentPage
{
public ScrollHeadingView()
{
// more code here
if (RightIconVisible)
{
// do some actions to add elements to each other here
var rightIconPageHeadingSvg = new PageHeadingSvg() { HorizontalOptions = LayoutOptions.StartAndExpand }
.Bind(PageHeadingSvg.SourceProperty, nameof(RightIconSource), source: this);
grid3.AddChild(rightIconPageHeadingSvg);
grid2.GestureRecognizers.Add(rightIconTap);
grid3.AddChild(rightIconPageHeadingSvg);
}
}
}
and
public partial class DecksTabPage : ScrollHeadingView
{
public DecksTabViewModel vm;
public DecksTabPage()
{
RightIconVisible = true;
BindingContext = vm = new DecksTabViewModel();
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
vm.OnAppearing();
}
}
I am setting RightIconVisible to true and want that to be reflected in ScrollHeadingView when it is constructed.
But it's not happening.
Is there a way that I can set this to happen with code in DecksTabPage?
Note that if I was using XAML I would have done this by binding to the IsVisible of a grid and then set the IsVisible to show or not show that code that has the elements. But as I am using C# I am not sure how to do this.
Constructor of parent class works before any code in constructor of child class.
To pass some value to parent class constructor, you should add a constructor parameter to it and call base constructor from child with needed value.
For example:
public partial class ScrollHeadingView : ContentPage
{
public ScrollHeadingView(bool rightIconVisible = false)
{
RightIconVisible = rightIconVisible;
if (RightIconVisible)
{
// do some actions to add elements to each other here
}
}
}
public partial class DecksTabPage : ScrollHeadingView
{
public DecksTabViewModel vm;
public DecksTabPage():base(true)
{
BindingContext = vm = new DecksTabViewModel();
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
vm.OnAppearing();
}
}
Other possibility is to remove code working with RightIconVisible from parent class constructor and put it into set of corresponding property.
public partial class ScrollHeadingView : ContentPage
{
private bool rightIconVisible;
public ScrollHeadingView()
{}
protected bool RightIconVisible
{
get => rightIconVisible;
set
{
rightIconVisible = value;
if (rightIconVisible)
{
// do some actions to add elements to each other here
}
}
}
public partial class DecksTabPage : ScrollHeadingView
{
public DecksTabViewModel vm;
public DecksTabPage()
{
RightIconVisible = true;
BindingContext = vm = new DecksTabViewModel();
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
vm.OnAppearing();
}
}

How to use a parameter in a Task-Based DelegateCommand in Prism

Can I use a parameter in a task-based DelegateCommand (Prism.Commands):
(https://prismlibrary.com/docs/commanding.html)
public class ArticleViewModel
{
public DelegateCommand SubmitCommand { get; private set; }
public ArticleViewModel()
{
SubmitCommand = new DelegateCommand<object>(async ()=> await Submit());
}
Task Submit(object parameter)
{
return SomeAsyncMethod(parameter);
}
}
Can I use a parameter in a task-based DelegateCommand?
Sure.
internal class ArticleViewModel : BindableBase
{
public ArticleViewModel()
{
SubmitCommandWithMethodGroup = new DelegateCommand<object>( SomeAsyncMethod );
SubmitCommandWithLambda = new DelegateCommand<object>( async x => { var y = await Something(x); await SomethingElse(y); } );
}
public DelegateCommand<object> SubmitCommandWithMethodGroup { get; }
public DelegateCommand<object> SubmitCommandWithLambda { get; }
}

Implementing a UISearchController using MVVMCross

I have an app which I am converting from iOS only to iOS & Droid using MVVMCross.
In my current app I have a map view that uses a UISearchController that allows the user to search for locations nearby. This is based on the Xamarin example and works fine:
Xamarin Map Example
For the conversion I have:
a MapView bound to a MapViewModel.
A search service which is injected into MapViewModel.
Created a UISearchController and bound the search text to a property on the MapViewModel.
When the text is updated the search is called and the results are retrieved. What I am struggling with is how to bind the results back to a SearchResultsView as this is presented by the UISearchController.
Can anyone give me advice or point me in the right direction to solve this.
I have the code snippet below to give an idea of what I have relied so far.
[MvxFromStoryboard]
public partial class MapView : MvxViewController<MapViewModel>
{
public MapView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var searchResultsController = new SearchResultsView();
//Not sure if this is required
//var searchUpdater.UpdateSearchResults += searchResultsController.Search;
var searchController = new UISearchController(searchResultsController)
{
//Nore sure if this is required
//SearchResultsUpdater = searchUpdater
};
searchController.SearchBar.SizeToFit();
searchController.SearchBar.SearchBarStyle = UISearchBarStyle.Minimal;
searchController.SearchBar.Placeholder = "Enter a search query";
searchController.HidesNavigationBarDuringPresentation = false;
DefinesPresentationContext = true;
NavigationItem.TitleView = searchController.SearchBar;
//Bind to View Model
var set = this.CreateBindingSet<MapView, MapViewModel>();
set.Bind(searchController.SearchBar).To(vm => vm.SearchQuery);
set.Apply();
}
}
public class SearchResultsUpdator : UISearchResultsUpdating
{
public event Action<string> UpdateSearchResults = delegate { };
public override void UpdateSearchResultsForSearchController(UISearchController searchController)
{
this.UpdateSearchResults(searchController.SearchBar.Text);
}
}
[MvxFromStoryboard]
public partial class SearchResultsView : MvxTableViewController<SearchResultsViewModel>
{
public SearchResultsView() { }
public SearchResultsView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new SearchResultsTableViewSource(TableView);
TableView.Source = source;
var set = this.CreateBindingSet<SearchResultsView, SearchResultsViewModel>();
set.Bind(source).To(vm => vm.Results);
set.Apply();
}
}
[MvxFromStoryboard]
public partial class SearchResultsView : MvxTableViewController<SearchResultsViewModel>
{
public SearchResultsView() { }
public SearchResultsView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
var source = new SearchResultsTableViewSource(TableView);
TableView.Source = source;
var set = this.CreateBindingSet<SearchResultsView, SearchResultsViewModel>();
set.Bind(source).To(vm => vm.Results);
set.Apply();
}
}
I have posted this in case someone else is looking for an example. I decided the best way to do this was to let iOS handle the search view controller for the results. Code follows. Feel free to correct or suggest a better alternative
View
[MvxFromStoryboard]
public partial class MapView : MvxViewController
{
UISearchController _searchController;
SearchResultsViewController _searchResultsController;
private IDisposable _searchResultsUpdateSubscription;
private IMvxInteraction _searchResultsUpdatedInteraction;
public IMvxInteraction SearchResultsUpdatedInteraction
{
get => _searchResultsUpdatedInteraction;
set
{
if (_searchResultsUpdateSubscription != null)
{
_searchResultsUpdateSubscription.Dispose();
_searchResultsUpdateSubscription = null;
}
_searchResultsUpdatedInteraction = value;
if (_searchResultsUpdatedInteraction != null)
{
_searchResultsUpdateSubscription = _searchResultsUpdatedInteraction.WeakSubscribe(OnSearchResultsUpdated);
}
}
}
private void OnSearchResultsUpdated(object sender, EventArgs e)
{
_searchResultsController.SearchResults = Results;
_searchResultsController.ReloadSearchTable();
}
public List<Placemark> Results { get; set; }
public MapView(IntPtr handle) : base(handle)
{
}
public override void ViewDidLoad()
{
base.ViewDidLoad();
//Bind to View Model
var set = this.CreateBindingSet<MapView, MapViewModel>();
set.Bind(_searchController.SearchBar).To(vm => vm.SearchQuery);
set.Bind(this).For(v => v.Results).To(vm => vm.Results);
set.Bind(this).For(v => v.SearchResultsUpdatedInteraction).To(vm => vm.SearchResultsUpdatedInteraction).OneWay();
set.Apply();
}
ViewModel
public class MapViewModel : MvxViewModel
{
readonly ILocationService _locationService;
private MvxInteraction _searchResultsUpdatedInteraction = new MvxInteraction();
public IMvxInteraction SearchResultsUpdatedInteraction => _searchResultsUpdatedInteraction;
public MapViewModel(ILocationService locationService)
{
_locationService = locationService;
}
//***** Properties *****
private List<Placemark> _results;
public List<Placemark> Results
{
get => _results;
set
{
_results = value;
RaisePropertyChanged();
}
}
private string _searchQuery;
public string SearchQuery
{
get => _searchQuery;
set
{
_searchQuery = value;
//Task.Run(UpdateResultsAsync).Wait();
RaisePropertyChanged();
UpdateResultsAsync();
}
}
//***** Privates *****
private async Task UpdateResultsAsync()
{
Results = await _locationService.SearchForPlacesAsync(_searchQuery);
_searchResultsUpdatedInteraction.Raise();
}
}
SearchResultsViewController
public class SearchResultsViewController : UITableViewController
{
static readonly string mapItemCellId = "mapItemCellId";
public List<Placemark> SearchResults { get; set; }
public SearchResultsViewController()
{
SearchResults = new List<Placemark>();
}
public override nint RowsInSection(UITableView tableView, nint section)
{
return SearchResults == null ? 0 : SearchResults.Count;
}
public override UITableViewCell GetCell(UITableView tableView, NSIndexPath indexPath)
{
var cell = tableView.DequeueReusableCell(mapItemCellId);
if (cell == null)
cell = new UITableViewCell();
cell.TextLabel.Text = SearchResults[indexPath.Row].FeatureName;
return cell;
}
public override void RowSelected(UITableView tableView, NSIndexPath indexPath)
{
//Do stuff here
}
public void ReloadSearchTable()
{
this.TableView.ReloadData();
}
}

Binding to a Xamarin Listview via web service

I am trying to launch a interface method and bind it to a Xamarin list view but I am having some trouble. My interface is below
readonly string url = "http://myinternaliis/api/";
readonly IHttpService httpService;
public ApiClient(IHttpService httpService)
{
this.httpService = httpService;
}
public Task<List<JobsList>> GetJobs() => httpService.Get<List<JobsList>>($"{url}job");
I am trying to bind it to my list view as such please correct me if this is wrong. Should I be creating a collection of some description
public partial class JobsPage : ContentPage
{
readonly string url = "http://myinternaliis/api/";
public IHttpService httpService;
public IApi FuleApiClient;
public JobsPage ()
{
InitializeComponent ();
FuelApiClient _client = new FuelApiClient(httpService);
this.JobListing.ItemsSource = _client.GetJobs();
}
You need to await your task.
public partial class JobsPage : ContentPage
{
readonly string url = "http://myinternaliis/api/";
public IHttpService httpService;
public IApi FuleApiClient;
public JobsPage ()
{
InitializeComponent ();
FuelApiClient _client = new FuelApiClient(httpService);
SetItemSource();
}
private Task SetItemSource()
. {
. JobListing.ItemsSource = await _client.GetJobs();
}
}

How to load a view in a particular region dynamically in Prism

In Prism Silverlight5, I have a shell which is divided into two vertical regions(leftRegion,rightRegion) & there are 2 views in Module1 i.e. (View1,View2). In leftRegion I have a View1 loaded which has a button. I want to dynamically load View2 on rightRegion using ViewModel & MEF.ViewModel code is :
[Export(typeof(LeftViewViewModel))]
public class LeftViewViewModel:ViewModelBase,IViewModel
{
[Import]
public IRegionManager CullingRegion { get; set; }
[ImportingConstructor]
public LeftViewViewModel(LeftView view)
:base(view)
{
LoadCommand = new DelegateCommand(LoadControl,CanLoadControl);
}
private void LoadControl()
{
CullingRegion.RegisterViewWithRegion("RightRegion", typeof(RightView));
}
protected bool CanLoadControl()
{
return true;
}
public DelegateCommand LoadCommand { get; set; }
}
LeftView.xaml.cs is :
[Import]
public ViewModels.IViewModel ViewModel
{
get { return (IViewModel) DataContext; }
set { DataContext = value; }
}
IModule implementation is :
[ModuleExport(typeof(CullingModuleModule1))]
public class CullingModuleModule1:IModule
{
[Import]
public IRegionManager CullingRegion { get; set; }
public void Initialize()
{
CullingRegion.RegisterViewWithRegion("ShellContentRegion", typeof (Container));
CullingRegion.RegisterViewWithRegion("LeftRegion", typeof(LeftView));
}
}
First of,I think your ViewModel should not be referenced by a View.
You may need to review View Injection with MEF.
As I've seen in multiple posts :
[Export]
public class YourViewClassName : UserControl
{
public YourViewClassName()
{
}
[Import]
public ILeftViewModel
{
get { return (ILeftViewModel )DataContext; }
set { DataContext = value; }
}
}
[Export(typeof(LeftViewViewModel))]
public class LeftViewViewModel : ILeftViewModel //ILeftViewModel inherits from IViewModel
{
public LeftViewViewModel()
{
}
}
Inside Module Initializer :
CullingRegion.Regions[YourRegionName].Add(ServiceLocator.Current.GetInstance<YourViewClassName>());
Hope it helps

Resources