How to make toolbar item in Carousel page go to other page - xaml

I have a carousel page and a home icon in the toolbar. When clicking the home icon, error appears.
Code in Xaml
<ContentPage.ToolbarItems>
<ToolbarItem Icon="home.png" Command="{Binding HomeCommand}"/>
</ContentPage.ToolbarItems>
Code in ViewModel
public class InfoItemViewModel : ObservableObject, INavigatedAware
{
public DelegateCommand HomeCommand { get; private set; }
private INavigationService mNavigationService;
public INavigationService navigationService;
public InfoItemViewModel(int productId, INavigationService navigationService)
: base(listenCultureChanges: true)
{
_productId = productId;
mNavigationService = navigationService;
HomeCommand = new DelegateCommand(HomeAction);
LoadData();
}
private async void HomeAction()
{
Debug.WriteLine("Button : Home");
await mNavigationService.NavigateAsync("DashboardMenuPage");
}
Code in Xaml.cs
public partial class InfoItem : ContentPage
{
public InfoItem()
: **this**(DataMyDOF.Products[0].Id)
{
}
public InfoItem(int productId, INavigationService navigationService)
{
InitializeComponent();
BindingContext = new InfoItemViewModel(productId, navigationService);
}
}
}
The error 'this'
There is no argument given that corresponds to the required formal
parameter ‘navigationService’ of ‘InfoItem.InfoItem(int,
INavigationService)’
After i add navigationService in the constructor, 'this' appears error. Actually I combine the carousel code from grial with prism. Products[0].Id is the array id. Should i add the navigation as the parameter too?
I tried to use Xamarin Forms Navigation. But still there's an error.
using Xamarin.Forms;
public INavigation Navigation { get; }
private void HomeAction()
{
Debug.WriteLine("Button : Home");
Navigation.PushAsync(new NavigationPage(new DashboardMenuPage()));
}
The error
System.NullReferenceException has been thrown Object reference not set
to an instance of an object

Related

Xamarin forms how to keep label text value after navigation

On my Main Page, I will set my label text (#lblStartDateTime) to current time stamp when user click on a button. It will navigate to Second Page, and once I click "done" button, it will go back to Main Page.
When I navigate back to Main Page from Second Page, my label text disappeared. Does anyone know how to keep the label text value after navigation?
Main Page
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Test
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MainPage: ContentPage
{
public string previouspagevalue;
public MainPage()
{
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
lblEndDT.Text = previouspagevalue;
}
private void btnOffline_Clicked(object sender, EventArgs e)
{
Navigation.PushAsync(new SecondPage());
string currentDT = DateTime.Now.ToString();
lblStartDT.Text = currentDT;
}
}
}
Second Page
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;
namespace Test
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class SecondPage: ContentPage
{
public SecondPage()
{
InitializeComponent();
}
protected void btnDone_Clicked(object sender, EventArgs e)
{
MainPage mainpage = new MainPage();
string edt = DateTime.Now.ToString();
lblEndDateTime.Text = edt;
mainpage.previouspagevalue = lblEndDateTime.Text;
Navigation.PushAsync(mainpage);
}
}
}
In you btnDone_Clicked event , you should use Navigation.PopAsync to go back to MainPage, Navigation.PushAsync(mainpage); means to go to a new MainPage not the previous Page.
protected void btnDone_Clicked(object sender, EventArgs e)
{
Navigation.PopAsync();
}
Please read document to learn about how NavigationPage works.
Update, you can pass the value you need to the SecondPage when you push to SecondPage:
Codes in MainPage:
public partial class MainPage : ContentPage
{
public string previouspagevalue;
public MainPage()
{
InitializeComponent();
previouspagevalue = "I'm previouspagevalue";
}
protected override void OnAppearing()
{
base.OnAppearing();
//if you set the lblEndDT.Text = "someValue"; in the secondPage, there is no need to update it here
lblEndDT.Text = previouspagevalue;
}
private void btnOffline_Clicked(object sender, EventArgs e)
{
//Pass the parametere you need when you go to SecondPage
Navigation.PushAsync(new SecondPage(this, lblEndDT));
string currentDT = DateTime.Now.ToString();
lblStartDT.Text = currentDT;
}
}
SecondPage:
public partial class SecondPage : ContentPage
{
Label MainPagelblEndDT;
MainPage mainPage;
public SecondPage()
{
InitializeComponent();
}
public SecondPage(MainPage mainP,Label lblEndDT)
{
InitializeComponent();
//Get the lblEndDT reference here
MainPagelblEndDT = lblEndDT;
//Get the MainPage reference here
mainPage = mainP;
}
private void Button_Clicked(object sender, EventArgs e)
{
string edt = DateTime.Now.ToString();
//Use it
MainPagelblEndDT.Text = edt;
mainPage.previouspagevalue = MainPagelblEndDT.Text;
Navigation.PopAsync();
}
}
I uploaded a sample project here and you can check it. Feel free to ask me any question if you have.
I would suggest you wrap your pages in a navigation stack, use a NavigationPage and navigate to next page then when you pop back it will maintain your state.
In your App.xaml.cs
MainPage = new NavigationPage(new YourFirstPage);
Then push a page into the navigation and when you want to go back just do a
Navigation.PopAsync();
Goodluck
Feel free to get back if you have queries

Xamarin forms Shell Custom icon for selected tab

I'm playing with the COOL xamarin shell, but I didn't found a way to change icon of the selected tab.
<TabBar Route="sections">
<Tab Title="home">
<Tab.Icon>
<FontImageSource FontFamily="{StaticResource AppIcons}" Glyph="{x:Static framework:Icons.HomePage}" />
</Tab.Icon>
<ShellContent ContentTemplate="{DataTemplate home:HomePage}" Route="home" />
</Tab>
The goal is to use Icons.HomePageFilled instead of Icons.HomePage for this tab only when it's selected. Same logic should apply to other tabs.
I think I got lost in the solutions found on the web. They talk about Custom renderers(ShellTabLayoutAppearanceTracker), Visual states, effects etc ...
But I do not know if it is feasible and what is the ideal solution
You need to use custom renderer of shell to customize the tabbar selected icon on each platform.
In iOS, override the CreateTabBarAppearanceTracker method:
[assembly: ExportRenderer(typeof(AppShell), typeof(MyShellRenderer))]
namespace App30.iOS
{
public class MyShellRenderer : ShellRenderer
{
protected override IShellSectionRenderer CreateShellSectionRenderer(ShellSection shellSection)
{
var renderer = base.CreateShellSectionRenderer(shellSection);
if (renderer != null)
{
}
return renderer;
}
protected override IShellTabBarAppearanceTracker CreateTabBarAppearanceTracker()
{
return new CustomTabbarAppearance();
}
}
public class CustomTabbarAppearance : IShellTabBarAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(UITabBarController controller)
{
}
public void SetAppearance(UITabBarController controller, ShellAppearance appearance)
{
UITabBar myTabBar = controller.TabBar;
if (myTabBar.Items != null)
{
UITabBarItem itemOne = myTabBar.Items[0];
itemOne.Image = UIImage.FromBundle("tab_about.png");
itemOne.SelectedImage = UIImage.FromBundle("tab_feed.png");
UITabBarItem itemTwo = myTabBar.Items[1];
itemTwo.Image = UIImage.FromBundle("tab_feed.png");
itemTwo.SelectedImage = UIImage.FromBundle("tab_about.png");
//The same logic if you have itemThree, itemFour....
}
}
public void UpdateLayout(UITabBarController controller)
{
}
}
}
In Android, override the CreateBottomNavViewAppearanceTracker method:
[assembly: ExportRenderer(typeof(AppShell), typeof(MyShellRenderer))]
namespace App30.Droid
{
public class MyShellRenderer : ShellRenderer
{
public MyShellRenderer(Context context) : base(context)
{
}
protected override IShellBottomNavViewAppearanceTracker CreateBottomNavViewAppearanceTracker(ShellItem shellItem)
{
return new CustomBottomNavAppearance();
}
}
public class CustomBottomNavAppearance : IShellBottomNavViewAppearanceTracker
{
public void Dispose()
{
}
public void ResetAppearance(BottomNavigationView bottomView)
{
}
public void SetAppearance(BottomNavigationView bottomView, ShellAppearance appearance)
{
IMenu myMenu = bottomView.Menu;
IMenuItem myItemOne = myMenu.GetItem(0);
if (myItemOne.IsChecked)
{
myItemOne.SetIcon(Resource.Drawable.tab_about);
}
else
{
myItemOne.SetIcon(Resource.Drawable.tab_feed);
}
//The same logic if you have myItemTwo, myItemThree....
}
}
}
I uploaded a sample project here and you can check it.

tabbed page causing more time to load the application

I am trying develop a xamarin app which has tabbed pages.
I have 3 main tabs.Each page viewmodel contructor has 3-5 Api calls.So its taking more time(20s) to load my app(for opening).
mainpage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Myapplication.Views.MenuPage"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:android="clr-namespace:Xamarin.Forms.PlatformConfiguration.AndroidSpecific;assembly=Xamarin.Forms.Core"
xmlns:views="clr-namespace:Dyocense.Views"
android:TabbedPage.ToolbarPlacement="Bottom"
android:TabbedPage.IsSwipePagingEnabled="False"
>
<views:A Title="A" Icon="dsjsdsd_18dp.png" ></views:A>
<views:B Title="B" Icon="askjasa.png"></views:B>
<views:C Title="C" Icon="abc.png"></views:C>
<views:D Title="D" Icon="abc.png"></views:D>
</TabbedPage>
How to load only first tab(A) detail page on app loading and rest of the pages on tab changing.
A solution is to make the heavy pages load their content in a lazy manner, only when their tab becomes selected. This way, since these pages are now empty when TabbedPage is created, navigating to the TabbedPage suddenly becomes very fast!
1.create a behavior for the TabbedPage page, called ActivePageTabbedPageBehavior.
class ActivePageTabbedPageBehavior : Behavior<TabbedPage>
{
protected override void OnAttachedTo(TabbedPage tabbedPage)
{
base.OnAttachedTo(tabbedPage);
tabbedPage.CurrentPageChanged += OnTabbedPageCurrentPageChanged;
}
protected override void OnDetachingFrom(TabbedPage tabbedPage)
{
base.OnDetachingFrom(tabbedPage);
tabbedPage.CurrentPageChanged -= OnTabbedPageCurrentPageChanged;
}
private void OnTabbedPageCurrentPageChanged(object sender, EventArgs e)
{
var tabbedPage = (TabbedPage)sender;
// Deactivate previously selected page
IActiveAware prevActiveAwarePage = tabbedPage.Children.OfType<IActiveAware>()
.FirstOrDefault(c => c.IsActive && tabbedPage.CurrentPage != c);
if (prevActiveAwarePage != null)
{
prevActiveAwarePage.IsActive = false;
}
// Activate selected page
if (tabbedPage.CurrentPage is IActiveAware activeAwarePage)
{
activeAwarePage.IsActive = true;
}
}
}
2.define IActiveAware interface
interface IActiveAware
{
bool IsActive { get; set; }
event EventHandler IsActiveChanged;
}
3.create a base generic abstract class called LoadContentOnActivateBehavior
abstract class LoadContentOnActivateBehavior<TActivateAwareElement> : Behavior<TActivateAwareElement>
where TActivateAwareElement : VisualElement
{
public DataTemplate ContentTemplate { get; set; }
protected override void OnAttachedTo(TActivateAwareElement element)
{
base.OnAttachedTo(element);
(element as IActiveAware).IsActiveChanged += OnIsActiveChanged;
}
protected override void OnDetachingFrom(TActivateAwareElement element)
{
(element as IActiveAware).IsActiveChanged -= OnIsActiveChanged;
base.OnDetachingFrom(element);
}
void OnIsActiveChanged(object sender, EventArgs e)
{
var element = (TActivateAwareElement)sender;
element.Behaviors.Remove(this);
SetContent(element, (View)ContentTemplate.CreateContent());
}
protected abstract void SetContent(TActivateAwareElement element, View contentView);
}
4.the specialized LazyContentPageBehavior
class LazyContentPageBehavior : LoadContentOnActivateBehavior<ContentView>
{
protected override void SetContent(ContentView element, View contentView)
{
element.Content = contentView;
}
}
then we can use in xaml like this:
<TabbedPage>
<TabbedPage.Behaviors>
<local:ActivePageTabbedPageBehavior />
</TabbedPage.Behaviors>
<ContentPage Title="First tab">
<Label Text="First tab layout" />
</ContentPage>
<local:LazyLoadedContentPage Title="Second tab">
<ContentPage.Behaviors>
<local:LazyContentPageBehavior ContentTemplate="{StaticResource ContentTemplate}" />
</ContentPage.Behaviors>
<ContentPage.Resources>
<ResourceDictionary>
<DataTemplate x:Key="ContentTemplate">
<!-- Complex and slow to render layout -->
<local:SlowContentView />
</DataTemplate>
</ResourceDictionary>
</ContentPage.Resources>
</local:LazyLoadedContentPage>
</TabbedPage>
we moved the ContentPage complex layout to become a DataTemplate.
Here's the custom LazyLoadedContentPage page which is activation aware:
class LazyLoadedContentPage : ContentPage, IActiveAware
{
public event EventHandler IsActiveChanged;
bool _isActive;
public bool IsActive
{
get => _isActive;
set
{
if (_isActive != value)
{
_isActive = value;
IsActiveChanged?.Invoke(this, EventArgs.Empty);
}
}
}
}
SlowContentView do some complex things
public partial class SlowContentView : ContentView
{
public SlowContentView()
{
InitializeComponent();
// Simulating a complex view
...
}
}
you could refer to the link
As a workaround I created a new class extending the Xamarin.Forms.TabbedPage and I send a message every time one of the tabs is clicked (or in general is diplayed)
public enum TabbedPages
{
MyPage1 = 0,
MyPage2 = 1,
MyPage3 = 2,
}
public class BottomBarPage : Xamarin.Forms.TabbedPage
{
protected override void OnCurrentPageChanged()
{
base.OnCurrentPageChanged();
var newCurrentPage = (TabbedPages)Children.IndexOf(CurrentPage);
MessagingCenter.Send<Xamarin.Forms.TabbedPage>(this, newCurrentPage.ToString("g"));
}
}
and then on the view models used for each page loaded when clicking on the tab I subscribe to the message and call my APIs
public class MyPage2ViewModel
{
public MyPage2ViewModel()
{
MessagingCenter.Subscribe<TabbedPage>(this, TabbedPages.MyPage2 .ToString("g"), async (obj) =>
{
//API call
});
}
}

PRISM Xamarin - Working with tabbed pages (IActiveAware)

I have tab pages implementing different views, but I cannot initialize each of the tabs when navigating.
<TabbedPage.Children>
<tabPages:Page1/>
<tabPages:Page2/>
<tabPages:Page3/>
</TabbedPage.Children>
So what I did was to use IActiveAware as prism documentation suggested to know which tab page is currently active. So I have this class:
public abstract class TabbedChildViewModelBase : BaseViewModel, IActiveAware, INavigationAware, IDestructible
protected bool IsInitalized { get; set; }
private bool _IsActive;
public bool IsActive
{
get
{
return _IsActive;
}
set
{
SetProperty(ref _IsActive, value, RaiseIsActiveChanged);
}
}
public event EventHandler IsActiveChanged;
public virtual void OnNavigatingTo(NavigationParameters parameters)
{
}
protected virtual void RaiseIsActiveChanged()
{
IsActiveChanged?.Invoke(this, EventArgs.Empty);
}
public virtual void Destroy()
{
}
}
So each child view models inherits the child view model base:
public class Page1 : TabbedChildViewModelBase
{
public CurrentSeaServiceViewModel()
{
IsActiveChanged += HandleIsActiveTrue;
IsActiveChanged += HandleIsActiveFalse;
}
private void HandleIsActiveTrue(object sender, EventArgs args)
{
if (IsActive == false)
{
TestLabelOnly = "Test";
}
// Handle Logic Here
}
private void HandleIsActiveFalse(object sender, EventArgs args)
{
if (IsActive == true) return;
// Handle Logic Here
}
public override void Destroy()
{
IsActiveChanged -= HandleIsActiveTrue;
IsActiveChanged -= HandleIsActiveFalse;
}
}
The problem is, the child vm isn't initializing. Is there something needed in order to implement IActiveAware properly nor launching the IsActive property
I still used IActiveAware unfortunately, to make the childtabbedviewmodel work you need to bind the page to its own view model.
So here's what I did:
<TabbedPage.Children>
<views:ChildPage1>
<views:ChildPage1.BindingContext>
<viewModels:ChildPage1ViewModel/>
</views:ChildPage1.BindingContext>
</views:ChildPage1>
<views:ChildPage2>
<views:ChildPage2.BindingContext>
<viewModels:ChildPage2ViewModel/>
</views:ChildPage2.BindingContext>
</views:ChildPage2>
</TabbedPage.Children>
I used the property BindingContext of my views and
using IActiveAware I would also know what tab is currently active. Hope anyone helps this who finds trouble binding the child pages of a tab.

Xamarin MVVM push user data to viewmodel

like the title says I want to give through the user information to my viewmodel, but the problem is that the viewmodel is registered as a dependency and I am binding its content to the xaml page itself. How do I send the user information to the viewmodel itself?
Thank you!
Xaml.cs part:
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class Calendar : ContentPage
{
public Calendar(User user)
{
InitializeComponent();
FileImageSource image = new FileImageSource
{
File = "calendar.png"
};
Icon = image;// push user information to the ICalendarViewModel
BindingContext = AppContainer.Container.Resolve<ICalendarViewModel>();
}
}
Interface:
public interface ICalendarViewModel : INotifyPropertyChanged
{
}
Bootstrap part registering dependencies:
public class Bootstrap
{
public IContainer CreateContainer()
{
var containerBuilder = new ContainerBuilder();
RegisterDependencies(containerBuilder);
return containerBuilder.Build();
}
protected virtual void RegisterDependencies(ContainerBuilder builder)
{
builder.RegisterType<CalendarViewModel>()
.As<ICalendarViewModel>()
.SingleInstance();
}
}
CalendarViewModel: I do not know if this will help
public class CalendarViewModel : ViewModelBase, ICalendarViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
public string ErrorMessage { get; set; }
private CourseInformation _information;
private ICourseInformationRepository _repository;
public CalendarViewModel()
{
_repository = new CourseInformationRepository();
LoadData();
}
private ObservableCollection<CourseInformation> _courses;
public ObservableCollection<CourseInformation> Courses
{
get
{
return _courses;
}
set
{
_courses = value;
RaisePropertyChanged(nameof(Courses));
}
}
private void LoadData()
{
try
{
ObservableCollection<CourseInformation> CourseList = new ObservableCollection<CourseInformation>(_repository.GetAllCourseInformation());
Courses = new ObservableCollection<CourseInformation>();
DateTime date;
foreach (var course in CourseList)
{
string [] cour = course.Date.Split('/');
cour[2] = "20" + cour[2];
date = new DateTime(Convert.ToInt32(cour[2]), Convert.ToInt32(cour[1]), Convert.ToInt32(cour[0]));
if (date == DateTime.Now)//TESTING WITH TEST DATE, datetime.now
{
if (course.FromTime.Length < 4)
{
course.FromTime = "0" + course.FromTime;
}
if (course.UntilTime.Length < 4)
{
course.UntilTime = "0" + course.UntilTime;
}
course.FromTime = course.FromTime.Insert(2, ":");
course.UntilTime = course.UntilTime.Insert(2, ":");
Courses.Add(course);
}
}
}
catch (ServerUnavailableException e)
{
ErrorMessage = "Server is niet beschikbaar, ophalen van kalender is niet mogelijk.";
}
}
private void RaisePropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
Bootstrap binding in app.xaml.cs:
public partial class App : Application
{
public App()
{
InitializeComponent();
AppContainer.Container = new Bootstrap().CreateContainer();
MainPage = new LoginView();
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
I wanted to comment (not enough reputation) on #LeRoy, use a framework. I would recommend FreshMVVM and you can pass objects into the ViewModel and even pass in Services. It makes it all nice and clean, and it just works.
Should not your CalendarViewModel viewModel contain BindableBase ?
public class CalendarViewModel : BindableBase, ViewModelBase, ICalendarViewModel
what framework are you using? prism, freshmvvm.
Your View and Viewmodel is normally automatically handled by the framework, all you need to do is register your page.
Container.RegisterTypeForNavigation<Views.CalendarPage>();