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

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();
}
}

Related

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();
}
}

XamarinForms: how use a Custom Navigation Page

I order to have a cutom navigation on a specific view on my app,
I create a Custom Navigation ios:
public class CustomNavigationPageRenderer : NavigationRenderer
{
public override void ViewDidLoad()
{
base.ViewDidLoad();
UINavigationBar.Appearance.SetBackgroundImage(new UIImage(), UIBarMetrics.Default);
UINavigationBar.Appearance.ShadowImage = new UIImage();
UINavigationBar.Appearance.BackgroundColor = UIColor.Clear;
UINavigationBar.Appearance.TintColor = UIColor.White;
UINavigationBar.Appearance.BarTintColor = UIColor.Clear;
UINavigationBar.Appearance.Translucent = true;
}
}
and a common interface class:
public partial class CustomPage : NavigationPage
{
public CustomPage(): base()
{
InitializeComponent();
}
public CustomPage(Page root) : base(root)
{
InitializeComponent();
}
public bool IgnoreLayoutChange { get; set; } = false;
protected override void OnSizeAllocated(double width, double height)
{
if (!IgnoreLayoutChange)
base.OnSizeAllocated(width, height);
}
}
Now, In my specific view, how have I use it?
I need to set on false the original navigation? (NavigationPage.SetHasNavigationBar(this, false);​)
public MySpecificViewNeedCustoNAv()
{
CustomPage myNavigationPage = new CustomPage();
...
need to set on false the original navigation?
No , you can refer the following code.
For example
Creating the Root Page in App.cs
public App ()
{
MainPage = new CustomPage (new xxxpage());
}
Pushing Pages to the Navigation Stack in your page
async void ButtonClicked (object sender, EventArgs e)
{
await Navigation.PushAsync (new xxxpage());
}

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

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;
}));
}
}
}

NavigationService throws NullReferenceException

Using MVVM Light, I'm trying to develop a rather simple WP7 application. I've run into a problem using the navigation service. I can navigate to a page, but after pressing the back button I can't navigate to the same page again. NavigationService throws a NullReferenceException.
I have implemented my navigation using Messaging from the GalaSoft.MvvmLight.Messaging namespace. All my views inherits from a customized PhoneApplicationPage base class that registrers a listener on "NavigationRequest":
public class PhoneApplicationPage : Microsoft.Phone.Controls.PhoneApplicationPage
{
public PhoneApplicationPage() : base()
{
Messenger.Default.Register<Uri>(this, "NavigationRequest", (uri) => NavigationService.Navigate(uri));
}
}
From my view models I post Uri's to this listener:
SendNavigationRequestMessage(new Uri("/View/AppSettingsView.xaml", UriKind.Relative));
Like i said, this works except when navigating after pressing the Back button.
Why is this and how can I solve it?
Is there a better way to implement navigation using MVVM Light?
I'm using MVVM Light as well. I have a class called PageConductor, which is based on what John Papa (Silverlight MVP) from Microsoft uses. Here's the PageConductor Service I use
public class PageConductor : IPageConductor
{
protected Frame RootFrame { get; set; }
public PageConductor()
{
Messenger.Default.Register<Messages.FrameMessage>(this, OnReceiveFrameMessage);
}
public void DisplayError(string origin, Exception e, string details)
{
string description = string.Format("Error occured in {0}. {1} {2}", origin, details, e.Message);
var error = new Model.Error() { Description = description, Title = "Error Occurred" };
Messenger.Default.Send(new Messages.ErrorMessage() { Error = error });
}
public void DisplayError(string origin, Exception e)
{
DisplayError(origin, e, string.Empty);
}
private void OnReceiveFrameMessage(Messages.FrameMessage msg)
{
RootFrame = msg.RootFrame;
}
private void Go(string path, string sender)
{
RootFrame.Navigate(new Uri(path, UriKind.Relative));
}
public void GoBack()
{
RootFrame.GoBack();
}
}
In my MainPage.xaml.cs constructor, I have this, which creates an instance of my ContentFrame in my PageConductor service.:
Messenger.Default.Send(new Messages.FrameMessage() { RootFrame = ContentFrame });
I then use dependency injection to instantiate an instance of my PageConductor Service into my MainPage ViewModel. Here is my MainViewModel class:
protected Services.IPageConductor PageConductor { get; set; }
public RelayCommand<string> NavigateCommand { get; set; }
public MainViewModel(Services.IPageConductor pageConductor)
{
PageConductor = pageConductor;
RegisterCommands();
}
private void RegisterCommands()
{
NavigateCommand = new RelayCommand<string>(
(source) => OnNavigate(source));
}
private void OnNavigate(string sender)
{
PageConductor.GoToView(sender, "main");
}
Notice the instance of my PageConductorService as a parameter in my MainViewModel constructor method. I pass this in via my ViewModelLocator:
private readonly TSMVVM.Services.ServiceProviderBase _sp;
public ViewModelLocator()
{
_sp = Services.ServiceProviderBase.Instance;
CreateMain(_sp);
}
#region MainPageViewModel
public static MainViewModel MainStatic
{
get
{
Services.ServiceProviderBase SP = Services.ServiceProviderBase.Instance;
if (_main == null)
{
CreateMain(SP);
}
return _main;
}
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance",
"CA1822:MarkMembersAsStatic",
Justification = "This non-static member is needed for data binding purposes.")]
public MainViewModel Main
{
get
{
return MainStatic;
}
}
public static void ClearMain()
{
_main.Cleanup();
_main = null;
}
public static void CreateMain(Services.ServiceProviderBase SP)
{
if (_main == null)
{
_main = new MainViewModel(SP.PageConductor);
}
}
#endregion
For further reference, my Messages.FrameMessage class is simply:
internal class FrameMessage
{
public Frame RootFrame { get; set; }
}
I've had no issues with forward/back buttons.

Castle Windsor: master controller not resolving the registered components in the container

Having the code below in my Global.asax.cs and two controller (one based on a the other: MasterController) I don't seem to find how can I resolve the repository register in my WindsorContainer from the MasterController... the same applies in the HomeController and works perfectly... what am I doing wrong?
Global.asax.cs:
private IWindsorContainer _container;
protected void Application_Start()
{
InitializeContainer();
RegisterRoutes(RouteTable.Routes);
}
protected void Application_End()
{
this._container.Dispose();
}
protected void Application_EndRequest()
{
if (_container != null)
{
var contextManager = _container.Resolve<IContextManager>();
contextManager.CleanupCurrent();
}
}
private void InitializeContainer()
{
_container = new WindsorContainer();
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(_container));
// Register context manager.
_container.Register(
Component.For<IContextManager>()
.ImplementedBy<EFContextManager>()
.LifeStyle.Singleton
.Parameters(
Parameter.ForKey("connectionString").Eq(ConfigurationManager.ConnectionStrings["ProvidersConnection"].ConnectionString)
)
);
//Products repository
_container.Register(
Component.For<IProductRepository>()
.ImplementedBy<ProductRepository>()
.LifeStyle.Singleton
);
// Register all MVC controllers
_container.Register(AllTypes.Of<IController>()
.FromAssembly(Assembly.GetExecutingAssembly())
.Configure(c => c.LifeStyle.Transient)
);
}
Controller base:
public class MasterController : Controller
{
private IProductRepository _productRepository;
public ProductController(IProductRepository product)
{
_productRepository = product;
}
public ActionResult Index()
{
ViewData["product"] = _productRepository.FindOne(123);
return View();
}
}
Controller based on MasterController:
public class ProductController : MasterController
{
private IProductRepository _productRepository;
public ProductController(IProductRepository product)
{
_productRepository = product;
}
public ActionResult Search(int id)
{
ViewData["product"] = _productRepository.FindOne(id);
return View();
}
}
It is working as expected now and the ViewDatas are accessible from any controller/view.
First I created a public class where I store my Windsor container so it can be accessed from any controller:
public static class IOCcontainer
{
public static IWindsorContainer Container { get; set; }
}
Then in my global.asax.cs I have:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
InitializeContainer();
}
private void InitializeContainer()
{
_container = new WindsorContainer();
// Register context manager.
_container.Register(
Component.For<IContextManager>()
.ImplementedBy<EFContextManager>()
.LifeStyle.Singleton
.Parameters(
Parameter.ForKey("connectionString").Eq(ConfigurationManager.ConnectionStrings["ProvidersConnection"].ConnectionString)
)
);
//Products repository
_container.Register(
Component.For<IProductRepository>()
.ImplementedBy<ProductRepository>()
.LifeStyle.Singleton
);
// Register all MVC controllers
_container.Register(AllTypes.Of<IController>()
.FromAssembly(Assembly.GetExecutingAssembly())
.Configure(c => c.LifeStyle.Transient)
);
IOCcontainer.Container = _container; //set the container class with all the registrations
ControllerBuilder.Current.SetControllerFactory(new WindsorControllerFactory(_container));
}
So now in my master controller I can use:
public class MasterController : Controller
{
private IProductRepository g_productRepository;
public MasterController() : this(null,null,null,null,null)
{
}
public MasterController(IProductRepository productRepository)
{
g_productRepository = productRepository ?? IOCcontainer.Container.Resolve<IProductRepository>();
}
//I don't use an action here, this will make execute it for any action in any controller
protected override void OnActionExecuting(ActionExecutingContext context)
{
if (!(context.ActionDescriptor.ActionName.Equals("Index") && context.Controller.ToString().IndexOf("Home")>0)) {
//I now can use this viewdata to populate a dropdownlist along the whole application
ViewData["products"] = g_productRepository.GetProducts().ToList().SelectFromList(x => x.Id.ToString(), y => y.End.ToShortDateString());
}
}
}
Then the rest of controllers:
//will be based on MasterController
public class AboutController : MasterController
{
}
public ActionResult Index()
{
return View();
}
etc...
Probably not the most elegant way to do it but it will do until I find a better way or someone else brighten my mind up!

Resources