Windows Phone Back button and page instance creation - windows-phone-7

I need to recreate new page instance on every page load (also when user pressed Back button).
So I overrided OnBackKeyPress method:
protected override void OnBackKeyPress(CancelEventArgs e)
{
base.OnBackKeyPress(e);
if (NavigationService.CanGoBack) {
e.Cancel = true;
var j = NavigationService.RemoveBackEntry();
NavigationService.Navigate(j.Source);
NavigationService.RemoveBackEntry();
}
}
The problem is that I can't handle case when user press back button to close CustomMessageBox dialog. How can I check it? Or is there any way to force recreation of page instance when going back through history state?

Why do you need to recreate the page instance? If you are simply trying to re-read the data to be displayed, why not put the data loading logic into OnNavigatedTo()?
Assuming that is what you are actually trying to achieve, try something like this...
public partial class MainPage : PhoneApplicationPage
{
// Constructor
public MainPage()
{
InitializeComponent();
// don't do your data loading here. This will only be called on page creation.
}
protected override void OnNavigatedTo(NavigationEventArgs e)
{
LoadData();
base.OnNavigatedTo(e);
}
MyViewModel model;
async void LoadData()
{
model = new MyViewModel();
await model.LoadDataAsync();
}
}
If you also have specific logic that you need to run on first construction of the page vs. on a back key navigation, check the NavigationMode property of the NavigationEventArgs object that gets passed to OnNavigatedTo.
if(e.NavigationMode == NavigationMode.New)
{
//do what you need to do specifically for a new page instance
}
if (e.NavigationMode == NavigationMode.Back)
{
// do anything specific for back navigation here.
}

Ha, in the near thread, i have opposite question :)
What about MessageBox - it depends, which one are you using. It can be custom message box, for example. Anyway, try to check MessageBox.IsOpened (or alternative for your MessageBox) in your OnBackKeyPress().
Another solution is to use OnNavigatedTo() of the page you want to be new each time.
Third solution: in case you works with Mvvm Light, add some unique id in ViewModel getter, like
public MyViewModel MyViewModel
{
get
{
return ServiceLocator.Current.GetInstance<MyViewModel>((++Uid).ToString());
}
}
This would force to recreate new ViewModel each time, so you'd have different instance of VM, so you would have another data on the View.

Related

Xamarin form MessagingCenter Unsubscribe is not working as expected

Functionality written inside the MessagingCenter.Subscribe() is called multiple times when i navigate to and fro multiple times in the application. But each time before subscribing, i do unsubscribe to the same in constructor as follows, still it didn't worked.
MessagingCenter.Unsubscribe<SubmitPage>(this,"Save");
MessagingCenter.Subscribe<SubmitPage>(this, "Save", (sender) =>
{
DisplayToastOnSuccessfulSubmission();
});
In my application i have 6 pages(git) and i save the data in 6th page with MessagingCenter.Send and same will be subscribed in 2nd page and saved message will be displayed in 2nd page(after navigating to that page).
Now i navigate like 2->1->2->3->4->5->6 in this particular case DisplayToastOnSuccessfulSubmission() would be called two times(because Page2 constructor is called twice).
I even tried placing the same code in OnAppearing.
I can't unsubscribe in OnDisappear as I need the event wiring up to when I reach Page6 for save.
Reproduced the same behaviour in sample project and added here https://github.com/suchithm/MessageCenterSampleApp Drop box link
What is the proper way to do this?
But each time before subscribing, I do unsubscribe to the same in constructor as follows, still it didn't worked.
MessagingCenter.Subscribe() is called multiple times, because there are two instances of Page2 in your code, both of them use MessagingCenter.Subscribe() method, that's why the Unsubscribe didn't work.
You can modify page2() to a singleton to make sure there is only one instance of Page2 in your project, after that when you send a message,
the MessagingCenter.Subscribe() is called only once.
Page2.cs:
public static Page2 instance = new Page2();
public static Page2 GetPage2Instance()
{
if(instance == null)
{
return new Page2();
}
return instance;
}
private Page2()
{
InitializeComponent();
MessagingCenter.Unsubscribe<Page2>(this, "SaveToastPage2");
MessagingCenter.Subscribe<Page2>(this, "SaveToastPage2", (sender) =>
{
DisplayToastOnSuccessfulSubmission();
}
}
When you send a message :
MessagingCenter.Send(Page2.GetPage2Instance(), "SaveToastPage2");
EDIT :
Remember that declaring constructors of Page2 class to be private to make sure there is only one instance of Page2 in your project sure.
private Page2()
{
...
}
Modify your Page1.cs code :
async void Handle_Next(object sender, System.EventArgs e)
{
await App.NavigationRef.PushAsync(Page2.GetPage2Instance(), true);
}
I faced same issue. I solved issue by passing the same parameters inn subscribe and unsubscribing as well.
MessagingCenter.Subscribe<Page1, T>(this, "Listen", async (Page1 arg1, T
listenedString) =>
{
});
Unsubscribe like below
MessagingCenter.Unsubscribe<Page1, T>(this, "Listen");
I'm using this temporary solution.
I declared a static dictionary to storage my object (to this example I used an object type).
private static Dictionary<string, object> subscribedReferencePages = new Dictionary<string, object>();
And I always storage the last subscribed page reference.
Then I compare the page reference before triggering the message method to fire only the last one.
subscribedReferencePages[pageName] = this;
MessagingCenter.Subscribe<ViewModelBase>(this, pageName, async (sender) =>
{
if (!ReferenceEquals(sender, this))
{
return;
}
this.OnInitialized();
});
To call the message method I need to pass the dictionary as parameter (instead of the "this" reference).
MessagingCenter.Send(subscribedPages[pageName], keyPageName);
Instead of unsubscribing when you navigate TO a page,
unsubscribe when you navigate AWAY from the page. At that point your instance of 'this' is still the same 'this' you think it is.

How to intercept Navigation Bar Back Button Clicked in Xamarin Forms?

I have a xamarin form page where a user can update some data in a form. I need to intercept the Navigation Bar Back Button Clicked to warn the user if some data have not been saved.How to do it?
I'm able to intercept the hardware Bar Back Button Clicked in Android using the Android.MainActivity.OnBackPressed(), but that event is raised only on hardware Bar Back Button Clicked, not on Navigation Bar Back Button Clicked.
I tried also to override Xamarin.Forms.NavigationPageOnBackButtonPressed() but it doesn't work. Why?
Any one have already solved that issue?
I also tried by overriding OnDisappear(), there are two problems:
The page has already visually disappeared so the "Are you sure?" dialog appears over the previous page.
Cannot cancel the back action.
So, is it possible to intercept the navigation bar back button press?
I was able to show a confirmation dialog that could cancel navigation by overriding the following methods in the FormsApplicationActivity.
// navigation back button
public override bool OnOptionsItemSelected(IMenuItem item)
{
if (item.ItemId == 16908332)
{
var currentViewModel = (IViewModel)navigator.CurrentPage.BindingContext;
currentViewModel.CanNavigateFromAsync().ContinueWith(t =>
{
if (t.Result)
{
navigator.PopAsync();
}
}, TaskScheduler.FromCurrentSynchronizationContext());
return false;
}
else
{
return base.OnOptionsItemSelected(item);
}
}
// hardware back button
public async override void OnBackPressed()
{
var currentViewModel = (IViewModel)navigator.CurrentPage.BindingContext;
// could display a confirmation dialog (ex: "Cancel changes?")
var canNavigate = await currentViewModel.CanNavigateFromAsync();
if (canNavigate)
{
base.OnBackPressed();
}
}
The navigator.CurrentPage is a wrapper around the INavigation service. I do not have to cancel navigation from modal pages so I am only handling the NavigationStack.
this.navigation.NavigationStack[this.navigation.NavigationStack.Count - 1];
The easiest, as #JordanMazurke also somewhat mentions, since the event for the back button cannot be handled currently (other than the physical back button for Android), is to either:
NavigationPage.ShowHasBackButton(this, false)
Pushing a Modal instead of a Page
Then afterwards, you can add an ActionbarItem from where you can handle the Event.
I personally spoke to the iOS team from Xamarin concerning this matter, and they basically told me we shouldn't expect support for handling the Event for the BackButtonPressed in the NavigationBar. The reason being, that on iOS, it's bad practice for the users to receive a message when Back is pressed.
Best way I have found is adding my own NavigationRenderer to intercept the navigation methods and a simple Interface
[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomerMobile.Droid.NavigationPageRenderer))]
public class NavigationPageRenderer : Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer
{
public Activity context;
public NavigationPageRenderer(Context context)
: base(context)
{}
protected override Task<bool> OnPushAsync(Page page, bool animated) {...}
protected override Task<bool> OnPopToRootAsync(Page page, bool animated){...}
protected override Task<bool> OnPopViewAsync(Page page, bool animated)
{
// if the page implements my interface then first check the page
//itself is not already handling a redirection ( Handling Navigation)
//if don't then let the handler to check whether to process
// Navitation or not .
if (page is INavigationHandler handler && !handler.HandlingNavigation
&& handler.HandlePopAsync(page, animated))
return Task.FromResult(false);
return base.OnPopViewAsync(page, animated);
}
}
Then my INavigationHandler interface would look like this
public interface INavigationHandler
{
public bool HandlingNavigation { get; }
public bool HandlePopAsync(Xamarin.Forms.Page view, bool animated);
public bool HandlePopToRootAsync(Xamarin.Forms.Page view, bool animated);
public bool HandlePuchAsync(Xamarin.Forms.Page view, bool animated);
}
Finally in any ContentView, in this example when trying to navigate back I'm just collapsing a menu and preventing a navigation back.
public partial class MenusList : INavigationHandler
{
public bool HandlingNavigation { get; private set; }
public bool HandlePopAsync(Page view, bool animated)
{
HandlingNavigation = true;
try
{
if (Menu.Expanded)
{
Menu.Collapse();
return true;
}
else return false;
}
finally
{
HandlingNavigation = false;
}
}
}
This is an inherently difficult task and the only way I got around it was to remove the back button entirely and then handle the backwards navigation from a 'save' button.
I have done a brief search of the Xamarin.Forms forum and the following has been suggested:
public override bool OnOptionsItemSelected(Android.Views.IMenuItem item)
{
return false;
}
The link for the post is as follows:
https://forums.xamarin.com/discussion/21631/is-there-nay-way-of-cancelling-the-back-button-event-from-the-navigationpage
As has already been said - you cannot do this cross-platform. However, you can handle it natively with arguably not so much effort: https://theconfuzedsourcecode.wordpress.com/2017/03/12/lets-override-navigation-bar-back-button-click-in-xamarin-forms/
The article covers iOS and Android. If you have a UWP project you'll have to hammer your own solution for it.
Edit:
Here is the UWP solution! It actually turned out to be pretty easy – there is just one back button and it’s supported by Forms so you just have to override ContentPage’s OnBackButtonPressed:
protected override bool OnBackButtonPressed()
{
if (Device.RuntimePlatform.Equals(Device.UWP))
{
OnClosePageRequested();
return true;
}
else
{
base.OnBackButtonPressed();
return false;
}
}
async void OnClosePageRequested()
{
var tdvm = (TaskDetailsViewModel)BindingContext;
if (tdvm.CanSaveTask())
{
var result = await DisplayAlert("Wait", "You have unsaved changes! Are you sure you want to go back?", "Discard changes", "Cancel");
if (result)
{
tdvm.DiscardChanges();
await Navigation.PopAsync(true);
}
}
else
{
await Navigation.PopAsync(true);
}
}
Back button appearance and behavior can be redefined by setting the BackButtonBehavior attached property to a BackButtonBehavior object.
<Shell.BackButtonBehavior>
<BackButtonBehavior Command="{Binding GoBackCommand}"/></Shell.BackButtonBehavior>
GobackCommand defined in your View Model.
You can follow below link for more details:
https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/shell/navigation
In Xamarin.Forms, the Page class has an OnBackButtonPressed() method that you can tap into for all platforms

How to Cleanup a ViewModel in Mvvm Light?

I have a list of items that goes to another page, That page is hooked up to a view model. In the constructor of this view model I have code that grabs data from the server for that particular item.
What I found is that when I hit the back button and choose another item fromt hat list and it goes to the other page the constructor does not get hit.
I think it is because the VM is now created and thinks it does not need a new one. I am wondering how do I force a cleanup so that a fresh one is always grabbed when I select from my list?
I faced the same issue, that's how i solved it.
Have a BaseView class, override OnNavigatedTo
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (NavigatedToCommand != null && NavigatedToCommand.CanExecute(null))
NavigatedToCommand.Execute(null);
}
add DependencyProperty.
public static readonly DependencyProperty NavigatedToCommandProperty =
DependencyProperty.Register("NavigatedToCommand", typeof(ICommand), typeof(BaseView), null);
public ICommand NavigatedToCommand
{
get { return (ICommand)GetValue(NavigatedToCommandProperty); }
set { SetValue(NavigatedToCommandProperty, value); }
}
On the necessary pages, add to xaml (and, of course, inherit BaseView )
NavigatedToCommand="{Binding OnNavigatedToCommand}"
In the ViewModel, make command itself
public RelayCommand OnNavigatedToCommand
{ get { return new RelayCommand(OnNavigatedTo); } }
and implement method you want to call to update list
public async void OnNavigatedTo()
{
var result = await myDataService.UpdateMyList();
if (result.Status == OK)
MyList = result.List;
}
So, now, every time you navigate to page with list, inside of overriden OnNavigatedTo(), a NavigatedToCommand would be executed, which would execute OnNavigatedToCommand (which you set in xaml), which would call OnNavigatedTo, which would update your list.
A bit messy, but MVVM :)
EDIT: What about cleanings, they can be done in OnNavigatedFrom(), which works the same. Or OnNavigatingFrom(), which also can be useful in some cases.

lwuit change UI language

I use codenameone to develop my mobile application. In this application I implement some classes and codes manually for instance create all forms by hard coding not using codenameone designer for some reason.
By the way I wanted to navigate in forms like what codenameone use, so I use one variable from type of Form called it prevForm and when I want to open a form I set it to current form and then I show new form.
Ok, that is main scenario. In this application I wanna implement internationalization too, so I create my own hashtable (Farsi and English) for this application.
This is my problem:
How can I set or change language and apply it to forms that I opened?
Is my method for navigate between forms are good?
Here is my code:
public class BaseForm extends Form implements ActionListener {
public BaseForm(){
this.setLayout(new BoxLayout(BoxLayout.Y_AXIS));
}
Command exit, ok, back;
Form prevForm;
protected void initForm(){
}
protected void showForm(){
}
protected void showForm(final Form prevForm){
//String name = this.getName();
//if("Reminder".equals(name) || "3Transaction".equals(name))
{
this.prevForm = prevForm;
Form f = this;
back = new Command("Back");
//ok = new Command("Ok");
//delete = new Command("Delete");;
Button button = new Button("Button");
f.addCommand(back);
//f.addCommand(ok);
//f.addCommand(delete);
//f.addComponent(button);
f.addCommandListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (ae.getCommand().equals(back)) {
//Do Exit command code
System.out.println("Back pressed");
prevForm.showBack();
} else if (ae.getCommand().equals(ok)) {
//Do Start command code
System.out.println("Ok pressed");
}
}
});
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
//Do button code
System.out.println("Action performed");
}
});
}
showForm();
}}
for open nested form I use this code:
LanguageUI lang = new LanguageUI();
lang.showForm(this);
change language [form]:
protected boolean onBtnSave() {
if(isRbFarsiSelected()){
UIManager.getInstance().setResourceBundle(new CommonSettings().getFarsi());
}
else {
UIManager.getInstance().setResourceBundle(new CommonSettings().getEnglish());
}
return false;
}
I also hard code my UI on lwuit, and i have a variable parentForm on every class so i can easily show previous form. For language change i know there is Localization in the resource editor that you can make use of. Below is how you can access it. I guess the trick is how to set the content of the L10N in the res file in code? On the other hand you can create your own helper classes that mirror the methods below.
Resources theme = Resources.open("/theme.res");
theme.getL10N(id, locale);
theme.getL10NResourceNames();
theme.isL10N(name);
theme.listL10NLocales(id)

WP7 Navigation - NullReferenceException

I need to navigate to a certain page the first time my app is run, to gather login details etc. I'm using IsloatedStorageSettings to save a value to determine if this is the first run of the app or not, which works fine.
My problem is actually navigating to my 'first run' page when the app is run for the first time, using NavigationService, it seems NavigationService is not created at this point so is still null. When is NavigationService created or how can I work around this?
My code (in the constructor of my main page:
if ((bool)settings["firstRun"])
{
if (NavigationService != null)
{
NavigationService.Navigate(new Uri("/FirstRun.xaml", UriKind.Relative));
}
else
{
MessageBox.Show("Navigation service must be null?"); //always prompts
}
}
else
{
InitializeComponent();
}
Peter Torr has a great blog post on the ins and outs of redirecting for the initial navigation, though for user login I'd suggest that you either use a full screen popup or have a login control on your "normal" start page and toggle visibility based on your first run condition.
Add in class
private bool m_onNavigatedToCalled = false;
In ctor
this.LayoutUpdated += new EventHandler(MainPage_LayoutUpdated);
Then in code
void MainPage_LayoutUpdated(object sender, EventArgs e)
{
if (m_onNavigatedToCalled)
{
m_onNavigatedToCalled = false;
Dispatcher.BeginInvoke(() =>
{
if (NavigationService != null)
{
MessageBox.Show("Navigation not null?"); //always prompts
}
else
{
MessageBox.Show("Navigation service must be null?");
}
//StartApp(); do all stuff here to keep the ctor lightweight
}
);
}
}

Resources