Navigating back to different page (UWP, Template 10) - c#

I need to ignore a page while navigating back, I tried the following:
public override async Task OnNavigatingFromAsync(NavigatingEventArgs args)
{
if (args.NavigationMode == NavigationMode.Back)
{
args.Cancel = true;
NavigationService.Navigate(typeof(MainPage));
} else {
args.Cancel = false;
}
await Task.CompletedTask;
}
On the page that I am navigating away from, but it seems to only cancel the navigation.
What is the best way to tackle this issue?

In the page that you want to ignore, you can add the following method. This will allow to remove the current page from the navigation history.
protected override void OnNavigatedFrom(NavigationEventArgs e)
{
// here NavigationService is supposed to be an instance of Template10 INavigationService
var backStack = NavigationService.FrameFacade.BackStack;
if (backStack.Count > 0 && backStack.Last().SourcePageType == this.GetType())
{
backStack.Remove(backStack.Last());
}
base.OnNavigatedFrom(e);
}

Related

Huge memory usage in Xamarin

I have some problems running my app on some old Androiddevices, and I therefore downloaded a trail of Visual Studio Professionel, as it has Diagnostics Tools.
I tried doing some simple stuff in my app, and I find it is scaring, that Xamarin.Forms.BindableProperty+BindablePropertyContext takes a size (in bytes of course) of 2.196.088 in UWP, which you can see at the following screendump.
.
In the example I have justed navigated through 5 pages. On 2 of the pages there are ListViews, and one of them have been cleared 3 times, and filled with new data.
So do I have to call GC.Collect() after clearing the ListView?
I've had a similar issue - navigating through pages a couple of times caused an OutOfMemoryException. For me the solution was to implement custom render for page with explicit Dispose() call.
public class CustomPageRenderer : PageRenderer
{
private NavigationPage _navigationPage;
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
_navigationPage = GetNavigationPage(Element);
SubscribeToPopped(_navigationPage);
}
private void SubscribeToPopped(NavigationPage navigationPage)
{
if (navigationPage == null)
{
return;
}
navigationPage.Popped += OnPagePopped;
}
protected override void Dispose(bool disposing)
{
Log.Info("===========Dispose called===========");
base.Dispose(disposing);
}
private void OnPagePopped(object sender, NavigationEventArgs args)
{
if (args.Page != Element)
{
return;
}
Dispose(true);
_navigationPage.Popped -= OnPagePopped;
}
private static NavigationPage GetNavigationPage(Element element)
{
if (element == null)
{
return null;
}
while (true)
{
if (element.Parent == null || element.Parent.GetType() == typeof(NavigationPage))
{
return element.Parent as NavigationPage;
}
element = element.Parent;
}
}
}
You can also take a look here but you need to be careful with disposing images, it may cause some problems if their parent page is in navigation stack and you want to go back.

Why does the debugger say Frame.Content is null here

I'm used to WinForms, so this isn't behaving quite how I'd expect. In the code below, ActiveView is a Frame, Register is Page. I want to load the register page into the ActiveView and then change the text on a button. Even though the page loads, the debugger says that ActiveView.Content == null in SetCloseButtonText. Why is that?
private void btnRegister_Click(object sender, RoutedEventArgs e)
{
SwapActiveView(Register);
}
public void SwapActiveView(Page NewPage)
{
if (ActiveView.Content == null || !ActiveView.Content.Equals(NewPage))
{
if (ActiveView.Content != null)
{
PreviousViews.Add((Page)ActiveView.Content);
}
ActiveView.Content = NewPage;
}
else
{
ActiveView.Content = NewPage;
}
SetCloseButtonText();
}
private void SetCloseButtonText()
{
if (PreviousViews.Count == 0 && ActiveView.Content == null)
{
tbCloseButton.Text = "Close";
}
else
{
tbCloseButton.Text = "Back";
}
}
I ended up finding the answer. A frame navigates asynchronously, whether you call the navigate function or just change the content. So I simply needed to add the method and call my function then.
private void ActiveView_Navigated(object sender, NavigationEventArgs e)
{
SetCloseButtonText();
}

Xamarin.Forms.WebView.Navigating event raised on iOS for internal navigation

Let's say you want to prevent the user from navigating away from your Xamarin.Forms.WebView to an external page.
public App ()
{
var webView = new WebView
{
Source = new HtmlWebViewSource
{
Html = "<h1>Hello world</h1><a href='http://example.com'>Can't escape!</a><iframe width='420' height='315' src='https://www.youtube.com/embed/oHg5SJYRHA0' frameborder='0' allowfullscreen></iframe>"
}
};
webView.Navigating += WebView_Navigating;
MainPage = new ContentPage {
Content = webView
};
}
private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
// we don't want to navigate away from our page
// open it in a new page instead etc.
e.Cancel = true;
}
This works fine on Windows and Android. But on iOS, it doesn't load at all!
On iOS, the Navigating event gets raised even when loading the source from a HtmlWebViewSource, with a URL that looks something like file:///Users/[user]/Library/Developer/CoreSimulator/Devices/[deviceID]/data/Containers/Bundle/Application/[appID]/[appName].app/
Alright, so you can get around that with something like this:
private void WebView_Navigating(object sender, WebNavigatingEventArgs e)
{
if (e.Url.StartsWith("file:") == false)
e.Cancel = true;
}
The page finally loads on iOS. Yay. But wait! The embedded YouTube video doesn't load! That's because the Navigating event gets raised for the internal navigation of embedded resources like iframes and even external scripts (like Twitter's <script charset="utf-8" type="text/javascript" src="http://platform.twitter.com/widgets.js"></script>), but only on iOS!
I couldn't find a way to determine if the Navigating event was raised from internal navigation or because the user clicked a link.
How to get around this?
I am not sure if it is possible to detect in Xamarin Forms out of the box but the navigation type is easily determined using a custom renderer. In your custom iOS renderer, assign a WebViewDelegate and within that Delegate class, override ShouldStartLoad() like so:
public class CustomWebViewRenderer : WebViewRenderer {
#region Properties
public CustomWebView CustomWebViewItem { get { return Element as CustomWebView; } }
#endregion
protected override void OnElementChanged(VisualElementChangedEventArgs e) {
base.OnElementChanged(e);
if(e.OldElement == null) {
Delegate = new CustomWebViewDelegate(); //Assigning the delegate
}
}
}
internal class CustomWebViewDelegate : UIWebViewDelegate {
public override bool ShouldStartLoad(UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) {
if(navigationType == UIWebViewNavigationType.LinkClicked) {
//To prevent navigation when a link is click, return false
return false;
}
return true;
}
}
You could also surface a bool property or even an enum back up to your Xamarin Forms WebView which would say whether the Navigating event was from a link being clicked or from something else, though a custom renderer would be needed for that as well.
private bool isNavigated = false;
public CustomWebView()
{
if (Device.OS == TargetPlatform.Android)
{
// always true for android
isNavigated = true;
}
Navigated += (sender, e) =>
{
isNavigated = true;
};
Navigating += (sender, e) =>
{
if (isNavigated)
{
try
{
var uri = new Uri(e.Url);
Device.OpenUri(uri);
}
catch (Exception)
{
}
e.Cancel = true;
}
};
}

Bypass Page.VerifyRenderingInServerForm

I am trying to render a Wizard control to a HTML string on the click of a Button (using Control.Render). I am already disabling event validation with the following, which works fine and enables me to render the entire Page to the string. I do this within the user control that contains the Wizard:
protected void Page_Init(object sender, EventArgs e)
{
if (Request.Form["__EVENTTARGET"] != null
&& Request.Form["__EVENTTARGET"] == btnPrint.ClientID.Replace("_", "$"))
{
Page.EnableEventValidation = false;
}
}
While this works, I'd like to render the Wizard control on its own. I understand that I can override Page.VerifyRenderingInServerForm in order to prevent the page from throwing an exception when I attempt to render this control on its own (without runat="server" form tags), like so:
public override void VerifyRenderingInServerForm(Control control)
{
// base.VerifyRenderingInServerForm(control);
}
However, I don't want to override this completely. Is there a way I can bypass this dynamically, either:
For the specific PostBack in which the button in question is clicked, or...
Specifically for the Wizard control?
How about something like:
public override void VerifyRenderingInServerForm(Control control)
{
if (!SkipVerifyRenderingInServerForm)
{
base.VerifyRenderingInServerForm(control);
}
}
public bool SkipVerifyRenderingInServerForm
{
get
{
object o = HttpContext.Current.Items["SkipVerifyRenderingInServerForm"];
return (o == null) ? false : (bool) o;
}
set
{
HttpContext.Current.Items["SkipVerifyRenderingInServerForm"] = value;
}
}
You could then set SkipVerifyRenderingInServerForm to true in your button click event handler.

How to disable Alt + arrow key frame navigation?

I started an WPF application in a mvvm way. The main window contains a frame-control for navigating through differnt pages. For this I use a simple NavigationService for now:
public class NavigationService : INavigationService
{
private Frame _mainFrame;
#region INavigationService Member
public event NavigatingCancelEventHandler Navigating;
public void NavigateTo(Uri uri)
{
if(EnsureMainFrame())
{
_mainFrame.Navigate(uri);
}
}
public void GoBack()
{
if(EnsureMainFrame() && _mainFrame.CanGoBack)
{
_mainFrame.GoBack();
}
}
#endregion
private bool EnsureMainFrame()
{
if(_mainFrame != null)
{
return true;
}
var mainWindow = (System.Windows.Application.Current.MainWindow as MainWindow);
if(mainWindow != null)
{
_mainFrame = mainWindow.NavigationFrame;
if(_mainFrame != null)
{
// Could be null if the app runs inside a design tool
_mainFrame.Navigating += (s, e) =>
{
if (Navigating != null)
{
Navigating(s, e);
}
};
return true;
}
}
return false;
}
}
On Page1 a button press forces the navigation to Page2 using th NavigationService.
On Page2 there is a TextBox. If the TextBox is focused i can use ALT + left arrow key to navigate back to Page1. How can I disable this behavior?
I tried setting KeyboardNavigation.DirectionalNavigation="None" in the frame-control and also in the TextBox-Control without success.
Add the following event handler to the textbox to disable the alt + left navigation:
private void textBox1_PreviewKeyDown(object sender, KeyEventArgs e)
{
if ((Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))
&& (Keyboard.IsKeyDown(Key.Left)))
{
e.Handled = true;
}
}
XAML
<TextBox ... KeyDown="textBox1_PreviewKeyDown" />
EDIT: changed to PreviewKeyDown in order to capture arrow key events

Categories

Resources