Xamarin Wrong Count of NavigationStack in OnAppearing after Navigatig Back - xamarin

I am using the following method to set the pagenumber of my current page:
protected override void OnAppearing()
{
base.OnAppearing();
// Page number
_pageNumber = Navigation.NavigationStack.Count - 2;
}
Everything works fine, if I am navigating forward and pushing a new page onto the stack like this:
private async void btnContinue_Clicked(object sender, EventArgs e)
{
// Proceed to next view
await Navigation.PushAsync(new MainPage());
}
However, when I am navigating back from one page, the OnAppearing() method seems to be called before the previous page is popped. Thus, the _pageNumber variable will effectively be the one of the previous page. How do I work around that? I thought of placing it inside the constructor but the NavigationStack.Count always returns 0 in there.
My pages are dynamically programmed, therefore, I cannot hardcode the page number into the page because the same page is called but the values are changing based on the number of the page.

This will be an expected effect .
When the PopAsync method is invoked, the following events occur:
The page calling PopAsync has its OnDisappearing override invoked.
The page being returned to has its OnAppearing override invoked.
The PopAsync task returns.
So if you want to get current count of NavigationStack , you could improve the answer like following :
In the demo , I used MessageingCenter to pass the current of NavigationStack to last page . And the label will display the current NavigationStack .
bool isFirstLoad = true;
protected override void OnAppearing()
{
base.OnAppearing();
if(isFirstLoad)
{
label.Text = Navigation.NavigationStack.Count.ToString();
isFirstLoad = false;
}
MessagingCenter.Subscribe<Object,int>(this, "pop", (arg, num) => {
label.Text = (num-1).ToString();
});
}
And when you call Pop
private async void Button_Clicked_1(object sender, EventArgs e)
{
MessagingCenter.Send<Object,int>(this, "pop", Navigation.NavigationStack.Count);
await Navigation.PopAsync();
}

Related

Can I change the UI from a method that gets data (not async) that has been called with await?

I have this code:
protected override async void OnAppearing()
{
base.OnAppearing();
Subscribe();
vm.IsBusy = true;
if (Change.firstTimeCardsTab == true)
{
SetButtons(Settings.cc.ShortText());
await Task.Run(() => GetOnAppearingData());
}
}
private void GetOnAppearingData()
{
App.DB.GetData();
AddDetailSection();
SetPageDetails();
Change.firstTimeCardsTab = false;
vm.IsBusy = false;
}
vm.IsBusy = true; shows an activity indicator on the page and vm.IsBusy = false; would I hoped turn it off.
However when I run the code I get a message saying:
UIKit Consistency error: you are calling a UIKit method that can only
be invoked from a UI thread.
Can someone give me advice on how I can set the activity indicator to show, then get the data and then set it to not show? Note that AddDetailSection(); adds some elements to the XAML. Could this be the problem?
UI can be manipulated only from the main thread. There is a method Device.BeginInvokeOnMainThread(Action) that can help to solve your issue, more can be found in the official documentation.
Just wrap all UI related actions by it:
Device.BeginInvokeOnMainThread(() => {
SetPageDetails();
// Any other actions
});

Windows Phone Back button and page instance creation

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.

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.

How to get user input from a popup control

I have a UserControl that utilizes a popup window in wp7. The user control has a text box for input, and a submit button. My issue is that the code does not halt once the popup is shown. It continues on through the code and does not wait for the user to press submit.
What is a good practice for making the code "halt" similar to a message box with an "Okay" button?
//my custom popup control
InputBox.Show("New Highscore!", "Enter your name!", "Submit");
string name = InputBox.GetInput();
//it does not wait for the user to input any data at this point, and continues to the next piece of code
if (name != "")
{
//some code
}
You could accomplish this with either an event, or an async method. For the event you would subscribe to a Closed event of your popup.
InputBox.Closed += OnInputClosed;
InputBox.Show("New Highscore!", "Enter your name!", "Submit");
...
private void OnInputClosed(object sender, EventArgs e)
{
string name = InputBox.Name;
}
You would fire the event when the user pushes the OK button
private void OnOkayButtonClick(object sender, RoutedEventArgs routedEventArgs)
{
Closed(this, EventArgs.Empty);
}
The other option is to use an async method. For this you need the async Nuget pack. To make a method async you use two main objects, a Task and a TaskCompletionSource.
private Task<string> Show(string one, string two, string three)
{
var completion = new TaskCompletionSource<string>();
OkButton.Click += (s, e) =>
{
completion.SetResult(NameTextBox.Text);
};
return completion.Task;
}
You would then await the call to the show method.
string user = await InputBox.Show("New Highscore!", "Enter your name!", "Submit");
I believe the Coding4Fun toolkit also has some nice input boxes

how to call PositionChanged method manually in windows phone?

I have the below event that gets fired upon geocoordinatewatcher object position changed event.
void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
//do the stuff here
}
Now when user clicks on any location on the map I want to call the above method and do the same stuff everytime.
Any idea how do I achieve this ?
Either call your event handler manually:
var position = new GeoPosition<GeoCoordinate>(DateTimeOffset.Now, new GeoCoordinate(32, 64));
this.watcher_PositionChanged(this, new GeoPositionChangedEventArgs<GeoCoordinate>(position));
Or rewrite your event handler to put the logic in another method, then call it:
void watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
{
this.UpdatePosition(e.Position);
}
private void UpdatePosition(GeoCoordinate coordinates)
{
// Do the stuff here
}
This way, you just have to call UpdatePosition whenever you feel like it. I'd recommend this solution, it's way cleaner than the first one.

Resources