OnBackButtonPressed not fired in ViewModel - events

I have a created a simple contentpage to test various events. When overriding OnBackButtonPressed in the ViewModel the event is not raised. According to xamarin the event will not be raised on iOS, but it should work on Android and WP. But I cant get it to work on these platforms either.
What am I missing?
TestPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="TestPage.TestPage"
Title="testPage">
</ContentPage>
TestPage.xaml.cs
public partial class TestPage : ContentPage
{
public TestPage ()
{
InitializeComponent();
this.BindingContext = new TestPageViewModel();
}
}
ViewModel
public class TestPageViewModel : ContentPage, INotifyPropertyChanged
{
public TestPageViewModel() { }
protected override bool OnBackButtonPressed()
{
// Do stuff
return base.OnBackButtonPressed();
}
}

Got an answer on the Xamarin forum. I have to put the OnBackPressed override in the codebehind of my xaml file, not in my view model:
public partial class MyPage2 : ContentPage
{
public MyPage2()
{
InitializeComponent();
this.BindingContext = new TestPageViewModel();
}
protected override bool OnBackButtonPressed()
{
// Do stuff
return base.OnBackButtonPressed();
}
}

Related

How can I set Shell.NavBarIsVisible="false" for a ContentPage in C#?

Here's the XAML I have:
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage
Shell.NavBarIsVisible="false"
xmlns ="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Test.ABC" >
I am trying to recreate this in C# but coming up with a problem as none of these methods work:
namespace Test
{
public partial class ABC : ContentPage
{
Shell.NavBarIsVisible = false;
public ABC()
{
Shell.SetNavBarIsVisible = false;
Shell.NavBarIsVisibleProperty = false;
Does anyone have any idea how I can do this in a C# implementation without the XAML file.
You need to invoke them in Method OnAppearing
in ContentPage
protected override void OnAppearing()
{
base.OnAppearing();
Shell.SetTabBarIsVisible(this, false);
Shell.SetNavBarIsVisible(this, false);
}
Note : If you only want to hide Tabbar and NavigationBar in a specific ContentPage , don't forget to display them when leaving the page
protected override void OnDisappearing()
{
base.OnDisappearing();
Shell.SetTabBarIsVisible(this, true);
Shell.SetNavBarIsVisible(this, true);
}

Pass OnAppearing to a ViewModel in Xamarin Forms MVVM?

I have business logic that loops around and does waits and other things. Currently this is in the code behind.
From what I have been able to read this is the wrong place and I should be placing this in the viewModel (correct me if wrong). If that's the case then should I have an OnAppearing method in my VM and if so how should I pass the OnAppearing to the View Model?
Currently my page OnAppearing looks like this:
protected async override void OnAppearing()
{
base.OnAppearing();
Title = Settings.mode.Text() + " Deck";
vm.LearnViewVisible = Settings.mode.IsLearn();
vm.PracticeViewVisible = Settings.mode.IsPractice();
vm.QuizViewVisible = Settings.mode.IsQuiz();
vm.QuizStartViewVisible = false;
If I am to be moving most of the business logic to the ViewModel then would that mean that all of this would move to an OnAppearing() method I create in the ViewModel?
Other way is using Behaviors.Forms from David Britch
...
<ContentPage.Behaviors>
<behaviors:EventHandlerBehavior EventName="Appearing">
<behaviors:InvokeCommandAction Command="{Binding PageAppearingCommand}" />
</behaviors:EventHandlerBehavior>
<behaviors:EventHandlerBehavior EventName="Disappearing">
<behaviors:InvokeCommandAction Command="{Binding PageDisappearingCommand}" />
</behaviors:EventHandlerBehavior>
</ContentPage.Behaviors>
...
Original
Or Xamarin Community Toolkit EventToCommandBehavior
<ContentPage.Behaviors>
<xct:EventToCommandBehavior
EventName="Appearing"
Command="{Binding PageAppearingCommand}" />
<xct:EventToCommandBehavior
EventName="Disappearing"
Command="{Binding PageDisappearingCommand}" />
</ContentPage.Behaviors>
Related Question: EventHandlerBehavior vs EventToCommandBehavior
This is how i link my Viewmodel. I would recommend setting up a ViewModelBase with : VModelActive and VModelInactive
Code Behind:
public partial class YourClass : ContentPage
{
ViewModelClass viewModelClass;
public YourClass()
{
InitializeComponent();
viewModelClass = new ViewModelClass();
this.BindingContext = viewModelClass;
}
protected override void OnAppearing()
{
base.OnAppearing();
viewModelClass.VModelActive(this, EventArgs.Empty);
}
protected override void OnDisappearing()
{
base.OnDisappearing();
viewModelClass.VModelInactive(this, EventArgs.Empty);
}
}
View Model
public override void VModelActive(Page sender, EventArgs eventArgs)
{
base.VModelActive(sender, eventArgs);
//your code
}
public override void VModelInactive(Page sender, EventArgs eventArgs)
{
base.VModelInactive(sender, eventArgs);
//your code
}
I prefer a pattern I first encountered in some Realm sample code.
A ViewModel base provides empty overrideable OnAppearing/Disappearing
public class BaseViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string name = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (Equals(field, value))
{
return false;
}
field = value;
OnPropertyChanged(propertyName);
return true;
}
internal virtual void OnAppearing() { }
internal virtual void OnDisappearing() { }
}
User classes descend from a base that conditionally invokes the VM.
public class BasePage : ContentPage
{
protected override void OnAppearing()
{
base.OnAppearing();
(BindingContext as BaseViewModel)?.OnAppearing();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
(BindingContext as BaseViewModel)?.OnDisappearing();
}
}
// used as
public class JournalEntryDetailsViewModel : BaseViewModel
Warning: if you change the base class like this you need to use it in the XAML - use a scoped version of BasePage instead of the <ContentPage top element.
Otherwise you will get an error [CS0263] Partial declarations of 'JournalEntriesPage' must not specify different base classes
<?xml version="1.0" encoding="UTF-8"?>
<v:BasePage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:v="clr-namespace:QuickJournal.Views"
x:Class="QuickJournal.Views.JournalEntriesPage"
Title="Journal"
x:Name="page">
<ContentPage.ToolbarItems>
<ToolbarItem Text="Add" Command="{Binding AddEntryCommand}" />
</ContentPage.ToolbarItems>
<ContentPage.Content>
Here is example from my solution
public partial class TaskDetailsPage : MvvmContentPage
{
private readonly TaskDetailsViewModel _model;
public TaskDetailsPage()
{
InitializeComponent();
Shell.SetNavBarIsVisible(this, true);
Shell.SetTabBarIsVisible(this, false);
_model = BindingContext as TaskDetailsViewModel;
}
protected override string NavigationRoute => UniqeCodes.Routes.TaskDetailsPage;
protected override void OnAppearing()
{
_model.Init();
}
}

My UI did not response for orientation change

I am developing a native mobile app for all platforms. I have created my own theme content page. Then after deployment on android when I make phone landscape it did not respond. what's the reason here.
Here is my base content page.
public abstract class BaseContentPage : ContentPage
{
public readonly BaseViewModel BaseViewModel;
protected bool _isNavigated = false;
public BaseContentPage(BaseViewModel baseViewModel)
{
BaseViewModel = baseViewModel;
}
public abstract void Navigate(SelectedItemChangedEventArgs e);
protected abstract override void OnAppearing();
protected override void OnDisappearing()
{
_isNavigated = true;
}
public BaseContentPage()
{
}
}
here Xaml
<views:BaseContentPage
xmlns:views="clr-namespace:DipsDemoXaml.Views"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:Resource="clr-namespace:DipsDemoXaml.Resources"
x:Class="DipsDemoXaml.Views.WardListPage" Title="{x:Static Resource:AppResources.WardListPageTitle}">
<StackLayout BackgroundColor="{StaticResource DefaultBackgroundColor}" Orientation="Vertical" x:Name="s1">
I even try this also in code behind constructor I call size changed and create a method called Wardpagesizechanged.
public WardListPage(WardListPageViewModel wardListViewModel) : base(wardListViewModel)
{
InitializeComponent();
this.SizeChanged += wardpagesizechanged;
}
Wardpagesizechanged method
private void wardpagesizechanged(object sender, EventArgs e)
{
if(this.Width> this.Height)
{
s1.Orientation = StackOrientation.Horizontal;
}
else
{
s1.Orientation = StackOrientation.Vertical;
}
}
what is the problem here, I am clueless

Xamarin forms - How do I set font for toolbaritems

I have a toolbar in my mainpage and would like to set the font for each toolbaritem (so I can set custom fonts or normal fonts). So far I have been trying to get this done by implementing my own effect and this code works for labels but not for toolbaritems.
Whenever I run this code the effects gets added to the toolbaritem, but the effect code in android doesn't get called.
Does anyone know why I am unable to set fonts for toolbaritems with effects?
GitHub: https://github.com/jashan07/CustomEffectTest
Edit:
Brad Dixon suggested using TitleView which allows me to place labels in my navigationbar. As mentioned before the effect works for every control except toolbaritems.
This is a workaround which works for now (don't know about the limits or effects this will have) but I am still wondering why this isn't working for toolbaritems.
(TitleView was introduced in Xamarin.Forms V3.2)
My effect class:
public static class ChangeFontEffect
{
public static readonly BindableProperty FontProperty = BindableProperty.CreateAttached("Font",
typeof(string),
typeof(ChangeToolbarFontEffect),
null, propertyChanged: OnFontChanged);
private static void OnFontChanged(BindableObject bindable, object oldValue, object newValue)
{
if(bindable is Label labelControl)
{
if (!labelControl.Effects.Any((e) => e is ChangeToolbarFontEffect))
labelControl.Effects.Add(new ChangeToolbarFontEffect());
}
else if(bindable is ToolbarItem toolbarItem)
{
if (bindable is ToolbarItem)
if (!toolbarItem.Effects.Any((e) => e is ChangeToolbarFontEffect))
toolbarItem.Effects.Add(new ChangeToolbarFontEffect());
}
return;
}
public static string GetFont(BindableObject view)
{
return (string)view.GetValue(FontProperty);
}
public static void SetFont(BindableObject view, string value)
{
view.SetValue(FontProperty, value);
}
class ChangeToolbarFontEffect : RoutingEffect
{
public ChangeToolbarFontEffect() : base("CustomEffectTest.ChangeToolbarFontEffect") { }
}
}
XAML
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:effects="clr-namespace:CustomEffectTest.Effects"
x:Class="CustomEffectTest.MainPage">
<ContentPage.ToolbarItems>
<ToolbarItem Text="normal text"></ToolbarItem>
<ToolbarItem Text="ﭗ" effects:ChangeFontEffect.Font="materialdesignicons-webfont.ttf"></ToolbarItem>
</ContentPage.ToolbarItems>
Android
[assembly: ResolutionGroupName("bottomNavTest")]
[assembly: ExportEffect(typeof(ChangeToolbarFontEffect), "ChangeToolbarFontEffect")]
namespace bottomNavTest.Droid
{
public class ChangeToolbarFontEffect : PlatformEffect
{
TextView control;
protected override void OnAttached()
{
try
{
control = Control as TextView;
Typeface font = Typeface.CreateFromAsset(Forms.Context.ApplicationContext.Assets, ChangeFontEffect.GetTextFont(Element));
control.Typeface = font;
}
catch(Exception e)
{
}
}
protected override void OnDetached()
{
}
protected override void OnElementPropertyChanged(PropertyChangedEventArgs args)
{
if (args.PropertyName == ChangeFontEffect.TextFontProperty.PropertyName)
{
var val = ChangeFontEffect.GetTextFont(Element);
if (val != null)
{
Typeface font = Typeface.CreateFromAsset(Forms.Context.ApplicationContext.Assets, ChangeFontEffect.GetTextFont(Element));
control.Typeface = font;
}
}
}
}
}

Xamarin ListView binding is not working

I have been trying to bind my ListView to my View model. The view model successfully retrieves 5 records from the database and the Listview seems to display 5 blank rows, however it is not showing binding for each field within each row.
I have spent a couple of days searching internet but I don't seem to be doing anything different. I was using master detail pages so I thought that it may be the issue so I set my Events page as first navigation page without master/detail scenario but to no avail. Please note that I am using Portable Ninject for my dependencies/IoC.
My App.Xamal.cs is is as follows:
public App (params INinjectModule[] platformModules)
{
InitializeComponent();
var eventsPage = new NavigationPage(new EventsPage());
//Register core services
Kernel = new StandardKernel(new MyAppCoreModule(), new MyAppNavModule(eventsPage.Navigation));
//Register platform specific services
Kernel.Load(platformModules);
//Get the MainViewModel from the IoC
eventsPage.BindingContext = Kernel.Get<EventsViewModel>();
((BaseViewModel)(eventsPage.BindingContext)).Init();
MainPage = eventsPage;
}
My EventsPage.Xaml is provided below:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MyApp.Views.EventsPage"
Title="Events">
<ContentPage.Content>
<ListView x:Name="Events" ItemsSource="{Binding Events}" >
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Label Text="{Binding EventID}" BackgroundColor="Red" TextColor="White"
FontAttributes="Bold" />
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</ContentPage.Content>
</ContentPage>
My EventsPage.xaml.cs is provided below:
namespace MyApp.Views
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class EventsPage : ContentPage, IBaseViewFor<EventsViewModel>
{
public EventsPage ()
{
InitializeComponent ();
}
EventsViewModel _vm;
public EventsViewModel ViewModel
{
get => _vm;
set
{
_vm = value;
BindingContext = _vm;
}
}
}
}
My EventsViewModel is as follows, it successfully retrieves 5 records and OnPropertyChanged is fired for Events property:
namespace MyApp.ViewModels
{
public class EventsViewModel : BaseViewModel, IBaseViewModel
{
ObservableCollection<Event> _events;
readonly IEventDataService _eventDataService;
public ObservableCollection<Event> Events
{
get { return _events; }
set
{
_events = value;
OnPropertyChanged();
}
}
public EventsViewModel(INavService navService, IEventDataService eventDataService) : base(navService)
{
_eventDataService = eventDataService;
Events = new ObservableCollection<Event>();
}
public override async Task Init()
{
LoadEntries();
}
async void LoadEntries()
{
try
{
var events = await _eventDataService.GetEventsAsync();
Events = new ObservableCollection<Event>(events);
}
finally
{
}
}
}
}
My BaseViewModel is as follows:
namespace MyApp.ViewModels
{
public abstract class BaseViewModel : INotifyPropertyChanged
{
protected INavService NavService { get; private set; }
protected BaseViewModel(INavService navService)
{
NavService = navService;
}
bool _isBusy;
public bool IsBusy
{
get
{
return _isBusy;
}
set
{
_isBusy = value;
OnPropertyChanged();
OnIsBusyChanged();
}
}
protected virtual void OnIsBusyChanged()
{
}
public abstract Task Init();
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
// Secod BaseViewModel abstract base class with a generic type that will be used to pass strongly typed parameters to the Init method
public abstract class BaseViewModel<TParameter> : BaseViewModel
{
protected BaseViewModel(INavService navService) : base(navService)
{
}
public override async Task Init()
{
await Init(default(TParameter));
}
public abstract Task Init(TParameter parameter);
}
}
IBaseViewModel is just a blank interface:
public interface IBaseViewModel
{
}
IBaseViewFor is given below:
namespace MyApp.ViewModels
{
public interface IBaseViewFor
{
}
public interface IBaseViewFor<T> : IBaseViewFor where T : IBaseViewModel
{
T ViewModel { get; set; }
}
}
My Event model is as follows:
namespace MyApp.Models
{
public class Event
{
public int EventID;
}
}
Finally, the image of the output, as you can see that 5 rows are created with red background but EventID is not binding in each row. I have checked the data and EventID is returned. I have even tried to manually add records into Events list but to no avail, see the manual code and image below:
async void LoadEntries()
{
try
{
Events.Add((new Event() { EventID = 1 }));
Events.Add((new Event() { EventID = 2 }));
Events.Add((new Event() { EventID = 3 }));
Events.Add((new Event() { EventID = 4 }));
Events.Add((new Event() { EventID = 5 }));
}
finally
{
}
}
I have spent a lot of time on it but unable to find a reason for this anomaly, can someone please cast a fresh eye and provide help!?
You can only bind to public properties - ie, you need a getter
public class Event
{
public int EventID { get; set; }
}

Resources