I have implemented secondary tiles in my application, so a user is able to pin a secondary tile to the start screen, and then navigate to a respective page in my application accordingly. This implementation works ok, except for when the user hits the back hardware button after navigating to a specific page from the pinned secondary tile, nothing happens? In fact, for a quick second the previous page in the application is actually shown, although the user is coming from the start screen. What would be the proper method to actually return to the start screen as the user would expect would happen (I am assuming this would be the proper back stack navigation)?
What I have is as follows, but only works during normal page navigation scenarios, not when the user is navigating to the SharePage from the start screen pinned tile.
MainPage.xaml.cs
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
string _title = null;
NavigationContext.QueryString.TryGetValue("Param", out _title);
if (_title != null)
{
switch (_title)
{
case "status":
this.NavigationService.Navigate(new Uri("/Views/ShareStatusPage.xaml", UriKind.Relative));
break;
case "link":
this.NavigationService.Navigate(new Uri("/Views/ShareLinkPage.xaml", UriKind.Relative));
break;
}
}
}
private void CreateLiveTile(TileItem item)
{
string tileParameter = "Param=" + item.Title.ToString();
ShellTile Tile = CheckIfTileExist(tileParameter); // Check if Tile's title has been used
if (Tile == null)
{
try
{
var LiveTile = new StandardTileData
{
Title = item.TileName,
//BackgroundImage = ((System.Windows.Media.Imaging.BitmapImage)hubtile.Source).UriSource,
BackgroundImage = new Uri(item.ImageUri.ToString(), UriKind.Relative),
//Count = 1,
BackTitle = item.TileName,
//BackBackgroundImage = new Uri("", UriKind.Relative),
BackContent = item.Message,
};
ShellTile.Create(new Uri("/MainPage.xaml?" + tileParameter, UriKind.Relative), LiveTile); //pass the tile parameter as the QueryString
}
catch (Exception)
{
MessageBox.Show("This tile could not be pinned", "Warning", MessageBoxButton.OK);
}
}
else
{
MessageBox.Show("This tile has already been pinned", "Notice", MessageBoxButton.OK);
}
}
private ShellTile CheckIfTileExist(string tileUri)
{
ShellTile shellTile = ShellTile.ActiveTiles.FirstOrDefault(tile => tile.NavigationUri.ToString().Contains(tileUri));
return shellTile;
}
SharePage.xaml.cs
protected override void OnBackKeyPress(System.ComponentModel.CancelEventArgs e)
{
base.OnBackKeyPress(e);
//return to the previous page in the phones back stack?
if (NavigationService.CanGoBack)
{
e.Cancel = true;
NavigationService.GoBack();
}
//else
//{
// ??
//}
}
So far, CreateLiveTile() method creates the secondary tile and then when that tile is pressed, MainPage is navigated to and then the querystring is checked in the MainPage OnNavigatedTo event and the respective page is then loaded based on what secondary tile was clicked. Once this is performed and the respective pages has loaded, I can no longer press the back button to get back to the start screen to follow the standard backstack behavior. How can I fix this?
According to your new update, lets have a look at this method:
private void CreateLiveTile(TileItem item)
{
string tileParameter = "Param=" + item.Title.ToString();
//...
if (Tile == null)
{
try
{
var LiveTile = new StandardTileData
{
//...
};
ShellTile.Create(new Uri("/MainPage.xaml?" + tileParameter, UriKind.Relative), LiveTile); //pass the tile parameter as the QueryString
//blah-blah-blah
}
Here you create a new tile and pass tileParameter to a MainPage. So, you navigate to main page, then detect the text in tile parameter, and navigate to the ShareLink or ShareStatus pages. That's why you have a dirty navigation stack.
Let me suggest you a way to avoid this situation:
private void CreateLiveTile(TileItem item)
{
var title = item.Title.ToString();
//...
if (Tile == null)
{
try
{
var LiveTile = new StandardTileData
{
//...
};
string page;
switch (title)
{
case "status":
page = "/Views/ShareStatusPage.xaml";
break;
case "link":
page = "/Views/ShareLinkPage.xaml");
break;
}
if(string.IsNullOrEmpty(page))
{
//handle this situation. for example: page = "/MainPage.xaml";
}
ShellTile.Create(new Uri(page, UriKind.Relative), LiveTile);
//blah-blah-blah
}
When user taps on your secondary tile he will be navigated directly to ShareLink or ShareStatus page. And a NavigationStack will be clean. When the user press Back button, the application will be closed and user will see a start screen (this is a right back button behaviour for secondary tiles).
p.s. Don't forget to start all your services or load all resources if you have ones. Because MainPage won't be created! Anyway every page of your application has to be able to itnitialize whole application, because you must support restoring from tombstoned state.
Feel free to ask for details if you need.
else you can use NavigationService.Navigate(homepageUri)
Why do you cancel a navigaion? Just remove OnBackKeyPress. You don't need it in this scenario.
Create a variable to track whether the navigation to the main page came from a navigation through a secondary tile. On MainPage.Load check that variable, and if the variable came through a secondary tile, remove the prior page from the back stack. It makes sense to leave from the main page back to the start menu, rather than go back through the secondary tile page. This is how MyStocks Portfolio (no, not one of my apps, but one I l
Here's a good blog on back button use:
http://blogs.msdn.com/b/ptorr/archive/2011/10/06/back-means-back-not-forwards-not-sideways-but-back.aspx
Related
I have a navigation page that sets up three pages. The first page loads, the user has to pick an option from a listview and then it loads the second page with PushAsync(). At this point the user can now navigate between the three pages by turning the clock face. If I call PopToRootAsync() on the second page it works fine. If the rotary clock face is turned clockwise it loads a third page via PushAsync().
The problem is if I call PopAsync() on that third page OR I change the PopToRootAsync() on the second page to a PopAsync(), the app crashes. I have no way to determine what the error is either as I just get segmentation fault and nothing is written to the Tizen log that is seemingly indicative of why it crashed.
Is there some reason that a PopAsync() would cause this? I know I saw some other articles this could occur if the MainPage is not loaded into a NavigationPage but I'm doing that. I've been looking through stuff and writing debug logs for days but have nothing to show for it. Any help would be more than appreciated. Thank you!
App.cs
public App()
{
MainPage = new NavigationPage(new ServerSelectionPage());
}
ServerSelection.cs
private void ServerSelection_OnItemTapped(object sender, ItemTappedEventArgs args)
{
App.SERVER = (Server)args.Item;
Navigation.PushAsync(new ArrowsPage());
}
PageBase.cs
public async void Rotate(RotaryEventArgs args)
{
Page _currentPage = Page.REMOTE_BUTTONS;
if (this.GetType() == typeof(ButtonsPage))
_currentPage = Page.REMOTE_BUTTONS;
else if (this.GetType() == typeof(ArrowsPage))
_currentPage = Page.REMOTE_ARROWS;
else
_currentPage = Page.SERVER_SELECTION;
// When rotating (previous rotation is ongoing, do nothing)
if (_rotating)
{
return;
}
_rotating = true;
if (!(App.SERVER is null))
{
if (_currentPage == Page.SERVER_SELECTION)
{
if (args.IsClockwise)
await Navigation.PushAsync(new ArrowsPage());
}
else if (_currentPage == Page.REMOTE_DIRECTIONAL)
{
if (args.IsClockwise)
await Navigation.PushAsync(new ButtonsPage());
else
await Navigation.PopToRootAsync();
}
else
{
try
{
if (!args.IsClockwise)
await Navigation.PopAsync(); // This will crash the app
}
catch(Exception ex)
{
Log.Debug(ex.Message);
}
}
}
_rotating = false;
}
After reading the comment by #vin about checking if the Navigation object is null I suddenly had the thought that it may not be the Navigation page but the RotaryFocusObject. So I changed
if (!args.IsClockwise)
await Navigation.PopAsync();
to
if (!args.IsClockwise)
{
RotaryFocusObject = null;
await Task.Delay(300);
await Navigation.PopAsync();
}
and it no longer crashes. The delay is necessary as if you call PopAsync right after setting the object to null it can still sometimes crash.
So apparently if you pop the current page it causes an error as the focus of the rotary dial is still set to the current navigation page. Why this error does not seemingly occur if you use the Navigation.PopToRootAsync() makes no sense to me.
Navigation.PopToRootAsync() and Navigation.PopToRootAsync() are causing destroy of Page renderer, and the Rotate handler is belong to Page Renderer,
If Page renderer was deleted before completion of Rotate Native Callback that is belong to Page renderer, it will be crashed.
await Task.Delay() let to returning native callback.
I just noticed a odd behavior when using the interactivePopGestureRecognizer to go back in my app.
Case scenario:
1) User Drags view from left to right he goes back one view "interactivePopGestureRecognizer".
2) User swipes up or down "await Navigation.PopAsync(false);" is called and user goes back one view.
3) if User does action "1" and then calls a new view and tries to go-back using action 2 a blank view is displayed.
This error is only appearing if the user uses action 1 and then tries to use action 2; app works fine if action 1 is never used or if only action 1 is used, no both.
I am using Xamarin.Forms and I tried to used "interactivePopGestureRecognizer.enabled = false", but I get an error every-time I tried. is there a difference between the two back navigations?
-------------UPDATE----------
After reading a lot and looking in the internet I found out that ~interactivePopGestureRecognizer.enabled = false only works if you use it inside the ~ViewWillAppear I created a custom renderer that applies this to every tableView in my app. I would still want to figure out why is the back swipe acting this way.
----UPDATE 2---
Just pressing the back button and then trying to call navigation.popasync is giving me a blank page too. this seems to be an error between Xamarin.Navigation and the iOS back function.
Page:
MessagingCenter.Subscribe<string>(this, "UpSwipe", async (sender) =>
{
try
{
//await Navigation.PopAsync(false);
Navigation.RemovePage(this);
}
catch (Exception e)
{
await DisplayAlert("IsLoading", e.ToString(), "OK");
}
});
MessagingCenter.Subscribe<string>(this, "DownSwipe", async (sender) =>
{
try
{
//await Navigation.PopAsync(false);
Navigation.RemovePage(this);
}
catch (Exception e)
{
await DisplayAlert("IsLoading", e.ToString(), "OK");
}
});
Renderer:
private void UpdateUp()
{
// Insert view of DetailLeft element into subview
// Add button to open Detail to parent navbar, if not yet there
// Add gesture recognizer for left swipe
//Console.WriteLine ("Left swipe");
if (!buttons[2].Selected && !buttons[1].Selected)
{
MessagingCenter.Send("Swiped Up", "UpSwipe");
}
}
private void UpdateDown()
{
// Insert view of DetailLeft element into subview
// Add button to open Detail to parent navbar, if not yet there
// Add gesture recognizer for left swipe
//Console.WriteLine ("Left swipe");
if (!buttons[2].Selected && !buttons[1].Selected)
{
MessagingCenter.Send("Swiped Down", "DownSwipe");
}
}
After hours of debugging I fixed my issue by adding a return statement inside my renderer. I was checking and adding some gestureRecognizers in my renderer so a return was not needed until this bug came out.
protected override void OnElementChanged (ElementChangedEventArgs<LRMasterDetailPage> e)
{
base.OnElementChanged (e);
if (e.OldElement != null)
{
return;
}
}
I searched everywhere, but i can't find a tutorial for my problem. I want to set an page to be shown, when the App is launched for the first time. something like th:
First launch:
Greeting.xaml>Setting.xaml>MainPage.xaml
Regular launch goes directly to MainPage.
how can i do this?
I didn't mean a Splashscreen, I mean a page, which is shown only the first time you launch the App, something like a little tutorial.
Your typical template-generated App.xaml.cs has something like this in its OnLaunched method:
if (rootFrame.Content == null)
{
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
This is where you navigate to your first page. To special-case a first run, do something like this instead:
if (rootFrame.Content == null)
{
IPropertySet roamingProperties = ApplicationData.Current.RoamingSettings.Values;
if (roamingProperties.ContainsKey("HasBeenHereBefore"))
{
// The normal case
rootFrame.Navigate(typeof(MainPage), e.Arguments);
}
else
{
// The first-time case
rootFrame.Navigate(typeof(GreetingsPage), e.Arguments);
roamingProperties["HasBeenHereBefore"] = bool.TrueString; // Doesn't really matter what
}
}
The greetings page should then navigate to your settings page, which should navigate to your main page.
And by using the roaming settings, the user won't see the first-time screen when she logs in to a different machine.
You can set the "first" page within the App.xaml.cs. Search for the OnLaunched void and change rootFrame.Navigate(typeof(MainPage)); to rootFrame.Navigate(typeof(Greeting)); or whtatever you like to call it.
The next step would be to check if the app launches for the first time. You can set an app setting to do that.
1. create the OnnavigatedTo void for your Greeting.xaml (just type "protected override void onna", IntelliSense will suggest it to you) and make is asynchron by inserting "async" after "protected", 2. use this code:
if (ApplicationData.Current.LocalSettings.Values.ContainsKey("isFirstLaunch"))
{
// if that's the first launch, stay, otherwise navigate to Settings.xaml
if (!(bool)ApplicationData.Current.LocalSettings.Values["isFirstLaunch"])
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => Frame.Navigate(typeof(Settings)));
}
}
else
{
ApplicationData.Current.LocalSettings.Values["isFirstLaunch"] = false;
}
I haven't tested the code but it should work. If it doesn't, just ask me.
Edit: here's a much better solution :D https://stackoverflow.com/a/35176403/3146261
I just wanted a Disclaimer to be accepted via a MessageBox
IPropertySet roamingProperties = ApplicationData.Current.RoamingSettings.Values;
if (!roamingProperties.ContainsKey("DisclaimerAccepted"))
{
var dialog = new MessageDialog(strings.Disclaimer);
dialog.Title = "Disclaimer";
dialog.Commands.Clear();
dialog.Commands.Add(new UICommand { Label = "Accept", Id = 0 });
dialog.Commands.Add(new UICommand { Label = "Decline", Id = 1 });
var result = await dialog.ShowAsync();
if ((int)result.Id == 1)
Application.Current.Exit();
roamingProperties["DisclaimerAccepted"] = bool.TrueString;
}
I placed it in App.xaml.cs inside of:
if (e.PrelaunchActivated == false)
{
<Inside here>
if (rootFrame.Content == null)
{
}
I'm having the same problem posed here:
http://social.msdn.microsoft.com/Forums/wpapps/en-us/af8615e7-8e90-4069-aa4d-3c4a84a6a3d0/windows-phone-8-fast-app-resume-with-deeplinks?forum=wpdevelop
I'm no C# or WP expert, so please bear with me.
I have secondary tiles which link to "/MainPage.xaml?id=XX".
I have fast app resume enabled. (ActivationPolicy="Resume" in the app manifest)
I only have one page in my app: MainPage.xaml.
Problem: When I resume the app using a secondary tile ("/MainPage.xaml?id=XX"), I get a brief view of the previous instance (that would have resumed) and then the MainPage initializes again, creating a new instance. In effect, the app is loading from scratch after giving me a peek of what was previously open.
That is obviously undesired behavior. I want to use the existing instance to perform my task.
Attempt 1:
Use e.Cancel = true; to cancel the navigation to the MainPage.xaml:
(using the App.xaml.cs code from the official Fast App Resume sample to identify how the app was launched)
...
else if (e.NavigationMode == NavigationMode.New && wasRelaunched)
{
// This block will run if the previous navigation was a relaunch
wasRelaunched = false;
if (e.Uri.ToString().Contains("="))
{
// This block will run if the launch Uri contains "=" (ex: "id=XX") which
// was specified when the secondary tile was created in MainPage.xaml.cs
sessionType = SessionType.DeepLink;
e.Cancel = true; // <======================== Here
// The app was relaunched via a Deep Link.
// The page stack will be cleared.
}
}
...
Problem: In doing so, my OnNavigatedTo event handlers never fire, so my query string is never parsed.
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
String navId;
if (e.NavigationMode != System.Windows.Navigation.NavigationMode.Back)
{
if (NavigationContext.QueryString.TryGetValue("id", out navId))
{
MessageBox.Show(navId.ToString()); // Not reached
}
}
...
Attempt 2:
Use e.Cancel = true; to cancel the navigation to the MainPage.xaml, AND pass the Uri to a method in MainPage:
// App.xaml.cs
...
else if (e.NavigationMode == NavigationMode.New && wasRelaunched)
{
// This block will run if the previous navigation was a relaunch
wasRelaunched = false;
if (e.Uri.ToString().Contains("="))
{
// This block will run if the launch Uri contains "=" (ex: "id=XX") which
// was specified when the secondary tile was created in MainPage.xaml.cs
sessionType = SessionType.DeepLink;
e.Cancel = true;
MainPage.GoToDeepLink(e.Uri); // <======================== Here
// The app was relaunched via a Deep Link.
// The page stack will be cleared.
}
}
...
// MainPage.xaml.cs
public static void GoToDeepLink(Uri uri) // <======================== Here
{
// Convert the uri into a list and navigate to it.
string path = uri.ToString();
string id = path.Substring(path.LastIndexOf('=') + 1);
MyList list = App.ViewModel.ListFromId(Convert.ToInt32(id));
pivotLists.SelectedItem = list;
}
Problem: I get an error that pivotLists is non-static and thus requires an object reference. I think that in order to get this to work I'd need to create a new instance of MainPage (MainPage newMainPage = new MainPage();) and call newMainPage.pivotLists.SelectedItem = list; -- BUT I don't know how to use newMainPage instead of the existing one/replace it... or if that's something I want/won't cause further problems/complications.
I don't know what the solution is to this problem, and I may be going in the completely wrong direction. Please keep all suggestions in simple terms with code examples if you can, I'm still learning.
Thanks for any help.
It seems that when you reopen your App from secondary tile, then it's reactivated and new instance of MainPage is created (even if there is one from previous run). If I understood you correctly, I've managed to do such a thing:
In app.xaml.cs:
I've added a variable which indicates if I should return to previous MainPage after Navigating from secondary tile - it needs to be static as I want to have access to it from MainPage
public static bool returnPage = false;
In RootFrame_Navigating I'm setting this variable to true in:
// ...
else if (e.NavigationMode == NavigationMode.New && wasRelaunched)
{
// This block will run if the previous navigation was a relaunch
wasRelaunched = false;
returnPage = true;
// ...
In ClearBackStackAfterReset - prevent from deleting the old Page, when returning:
// ...
if (e.NavigationMode != NavigationMode.New || returnPage)
return;
// ...
In MainPage.cs:
I've changed a little constructor, as I don't want to see a blink of a new Page:
public MainPage()
{
if (!App.returnPage)
InitializeComponent();
}
In MainPage I've also variable which is passed from secondary tile - it's also static, as I need only one instance of it:
private static string navId = "";
And the core of the trick - OnNavigatedTo:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (App.returnPage)
{
App.returnPage = false;
NavigationContext.QueryString.TryGetValue("id", out navId);
NavigationService.GoBack();
}
else if (e.NavigationMode != NavigationMode.Reset)
{
// normal navigation
}
}
It works like this:
when you launch normally your App, returnPage is false, everything goes normal
when you activate it from secondary tile few things happen:
1. first goes navigation to your previous page with NavigationMode.Reset - we are not interested in it, so I switched it off - nothing should happen
2. then program tries to create new instance of MainPage, but returnPage is true, and because of the if statement, InitializeComponent won't run. Just after this, in OnNavigatedTo, program saves passed querystring and Navigates Back to previous instance of MainPage - from previous run
3. at last we are navigating to right MainPage with NavigationMode.Back and we have our querystring saved in static variable.
You must be aware of two things: first - probably it can be little rebuild (I'm not sure if wasRelaunched is needed and so on) - you need to debug it and see of what you can get rid off. Second - you will probably need to test your App with Tombstone case.
Hope this helps.
For my programming class we are required to make a memory match game. When the player clicks on a panel an image is displayed. The player clicks on two panels, if the images match they are removed, if they are different they are turned face down.
The problem I am having with this is that only the image from the first panel gets displayed, even though I am using the same line of code to display the images from both panels, and use Thread.Sleep to pause the program after the second tile has been picked. I don't understand why this is happening, any help would be appreciated.
private void tile_Click(object sender, EventArgs e)
{
string tileName = (sender as Panel).Name;
tileNum = Convert.ToInt16(tileName.Substring(5)) - 1;
//figure out if tile is locked
if (panelArray[tileNum].isLocked == false)
{
pickNum++;
//although the following line of code is used to display the picture that is stored in the tile array
//what is happening is that it will only display the picture of the first tile that has been picked.
//when a second tile is picked my program seems to ignore this line completely, any ideas?
panelArray[tileNum].thisPanel.BackgroundImage = tiles[tileNum].tileImage;
if (pickNum == 1)
{
pick1 = tileNum;
panelArray[tileNum].isLocked = true;
}
else
{
pick2 = tileNum;
UpdateGameState();
}
}
}
private void UpdateGameState()
{
Thread.Sleep(1500);
if (tiles[pick1].tag == tiles[pick2].tag)//compares tags to see if they match.
{
RemoveTiles();
}
else
{
ResetTiles();
}
pickNum = 0;
guess += 1;
guessDisplay.Text = Convert.ToString(guess);
if (correct == 8)
{
CalculateScore();
}
}
try this:
(sender as Panel).BackgroundImage = tiles[tileNum].tileImage;
you have to be sure that your tile_Click method is associated to both panel...