Anyone tried this scenario before? I'm talking about latest Xamarin forms 2 targetting iOS.
I have a TabbedPage with 4 tabs, when the user looks at the first 2, there should not be a navigationbar - I got this working by setting the NavigationPage.SetHasNavigationBar(this, false) in the constructor of the TabbedPage.
Now by using the ExtendedTabbedPage of Xamarin forms labs, I can hook up to the change tab event called OnCurrentPageChanged() and in here I verify if I'm on one of the last 2 tabs and toggle the NavigationPage.SetHasNavigationBar(this, true).
This actually works great, except for 1 small detail. When the navigation bar gets shown, tab page shifts down and it hides the tabs at the bottom, so the user is unable to switch tabs at that moment.
When I set NavigationPage.SetHasNavigationBar(this, true) in the constructor of the tabbedpage and leave it for all tabs, the result is ok. Meaning I get a navigationheader and tabs visible on each tab.
Ok found a solution myself.
So the original setup was as follows, in the App.xaml.cs I initiated the Navigation through following code
NavigationPage navigationPage = new NavigationPage(new MainPage());
MainPage is a TabbedPage containing several children.
What I did was toggling the NavigationBar of that MainPage when switching from one child page to another
NavigationPage.SetHasNavigationBar(this, false);
NavigationPage.SetHasNavigationBar(this, true);
But that resulted in the child pages not being resized, leaving the tabbar at the bottom of the page invisible when the navigationbar came visible.
So the actual solution is to not set the MainPage as a NavigationPage, but wrap each child in a NavigationPage!
This way each child can have it's own NavigationBar an that keeps the size in reference to the MainPage correct.
So adding each child would be like this
this.Children.Add (new NavigationPage (new DiscoverPage ()){ Title = "Discover" });
Took me a while to figure this out because I normally would add the children inside the actual xaml of the MainPage but that way you can't wrap them in a NavigationPage, hence why I wrapped the MainPage itself inside the NavigationPage!
It could be some bug in computing correct size of tabbed page in NavigationPage when toggling NavagationBar visibility.
In order that I need that tabbed page must be in NavigationPage I came up with the next solution:
public class BaseNavigationPage : NavigationPage
{
public BaseNavigationPage() : base()
{
this.BarTextColor = Color.White;
}
public BaseNavigationPage(Page page) : base(page)
{
this.BarTextColor = Color.White;
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == nameof(CurrentPage))
{
Current = CurrentPage;
}
}
private void CurrentPage_PropertyChanging(object sender, PropertyChangingEventArgs e)
{
if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName && _bounds == Rectangle.Zero)
{
_bounds = CurrentPage.Bounds;
}
}
private void Current_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == NavigationPage.HasNavigationBarProperty.PropertyName)
{
var has = NavigationPage.GetHasNavigationBar(CurrentPage);
if (has)
{
CurrentPage.Layout(_bounds);
_bounds = Rectangle.Zero;
}
else
{
CurrentPage.Layout(this.Bounds);
}
}
}
private Page Current
{
get { return _current; }
set
{
if (_current != null)
{
_current.PropertyChanging -= CurrentPage_PropertyChanging;
_current.PropertyChanged -= Current_PropertyChanged;
}
_current = value;
if (_current != null)
{
_current.PropertyChanging += CurrentPage_PropertyChanging;
_current.PropertyChanged += Current_PropertyChanged;
}
}
}
private Page _current;
private Rectangle _bounds;
}
And use may BaseNavigationPage instead of NavigationPage and all works fine.
Related
How to add a floating action button to bottom navigation bar in IOS (Xamarin)?
Example of screen layout
In order to add navigation bar, using freshmvvm provided function "FreshTabbedNavigationContainer".
However, I have no idea where should I add a floating action button on top of bottom navigation bar with the following code. Please feel free to comment.
using FreshMvvm;
.....
public App()
{
InitializeComponent();
//MainPage = new MainPage();
if (Device.RuntimePlatform == Device.iOS)
{
var tabs = new FreshTabbedNavigationContainer("MyTabs");
tabs.AddTab<MainPageModel>("Home", "");
tabs.AddTab<MainPageModel>("Saved", "");
tabs.AddTab<MainPageModel>("Zoom", "");
tabs.AddTab<MainPageModel>("Notifications", "");
tabs.AddTab<MainPageModel>("Profile", "");
// Set the selected tab to the middle one.
tabs.SwitchSelectedRootPageModel<MainPageModel>();
MainPage = tabs;
}
}
BasePage (Customized base class of contentPage)
public class BasePage : ContentPage
{
public BasePage()
{
NavigationPage.SetHasNavigationBar(this, false);
}
}
I'm trying to create a modular way of loading pages, each page having it's own navigation menu item in the main window header and a separate frame to keep them always loaded in memory (and to play fancy animations etc.)
Here's the class that contains the loaded page.
public sealed class PageContainer : Frame
{
public string Title;
public PageContainer(string Page, bool CustomUri = false)
{
Visibility = System.Windows.Visibility.Collapsed;
NavigationUIVisibility = NavigationUIVisibility.Hidden;
Title = Page;
if(!CustomUri)
{
Navigate($"pack://lotus:,,,/Views/UserPages/{Page}.xaml", UriKind.Relative);
}
else
{
//todo
}
Console.WriteLine($"Navigation Item created {Title} with ?CustomUri: {CustomUri}");
}
public bool Visible
{
get => Visibility == System.Windows.Visibility.Visible;
set => Visibility = value ? System.Windows.Visibility.Visible : System.Windows.Visibility.Collapsed;
}
}
And here's how I'm creating the PageContainer(s)
Animations.PageTransitionAnimations AnimationLibrary;
public double _SlideAnimationLenght = 250;
public MainWindow()
{
InitializeComponent();
//Initialize Animation Library
AnimationLibrary = new Animations.PageTransitionAnimations(this);
InitializePage("Library");
InitializePage("Preferences");
InitializePage("Plugins");
InitializePage("Information");
foreach(PageContainer pp in LoadedContainers)
{
pp.Visible = true;
pp.BeginAnimation(MarginProperty, AnimationLibrary.ToLeft);
}
}
//Load a page and add it to navigation bar, if NoHeader, then don't add it to navigation
private void InitializePage(string page, bool noHeader = false)
{
if(!noHeader)
{
//Add page to navigation bar and set style
NavigationHeader.Children.Add(new NavigationItem(page)
{
Style = FindResource("HeaderMenu") as System.Windows.Style
});
}
//Add Page to PageContainer and Initialize it
PageContainer _page = new PageContainer(page);
_containers.Add(_page);
PagesContainer.Children.Add(_page);
}
//Public LoadedContainers, no setter
private List<PageContainer> _containers = new List<PageContainer>();
public List<PageContainer> LoadedContainers => _containers;
What happens is that the navigation in PageContainer actually succeeds the navigation event, but frames don't report any content, page code doesn't execute and there's no design.
Any suggestions? Thanks.
Edit:
Pages themselves have no problems, because I load them manually, they do work. Also, here's the link to full code: https://github.com/FaithLV/lotus
There was an issue with how I was navigating to the page.
Turns out, the "application" in URI wasn't context sensitive and actually is used as is.
Here's the correct way to navigation:
Navigate(new Uri($"pack://application:,,,/Views/UserPages/{Page}.xaml"), UriKind.Absolute);
I write my first WPF application, which consist of several pages:
Welcome page with some logo
Login page with login form
Main page with account info
MainWindow contains <Frame> WPF Control, and I use animation to show next/previous page.
I write my own MainAnimation class to perform animation.
This application works fine on my laptop, but when I try to run it on the machine of my friend animation just do nothing.
I think that trouble related with Dispatcher.Invoke() method calling, and I tried to find solution over the web (here here here and here) and I tried:
use Application.Current.Dispatcher
use Dispatcher.BeginInvoke() instead of Dispatcher.Invoke()
but it does nothing.
So, I show Welcome page only 2 seconds and Login page must loaded automatically.
This is the code of WelcomePage.xaml.cs file:
public partial class WelcomePage : Page {
public WelcomePage (MainWindow parent) {
InitializeComponent();
this.parent = parent;
Task.Factory.StartNew(() => ShowLoginForm());
}
private MainWindow parent;
private void ShowLoginForm()
{
Thread.Sleep(2000);
this.parent.GoToLoginForm();
}
}
This is the code of MainWindow.xaml.cs file:
public partial class MainWindow : Window {
public MainWindow () {
InitializeComponent();
animation = new MainAnimation(this, this, Main, new WelcomePage(this));
}
private MainAnimation animation;
public void GoToLoginForm() => animation.ShowNextPage(new LoginPage(this));
public void GoToVideosForm() => animation.ShowNextPage(new MainPage(this));
}
And this is related parts on MainAnimation class (MainAnimation.cs):
public class MainAnimation
{
public MainAnimation(FrameworkElement resourcesOwner, DispatcherObject dispatcherOwner, Frame currentPageContainer, Page firstPage)
{
this.resourcesOwner = resourcesOwner;
this.dispatcherOwner = dispatcherOwner;
this.currentPageContainer = currentPageContainer;
pages = new Stack<Page>();
pages.Push(firstPage);
currentPageContainer.Content = pages.Peek();
}
private Stack<Page> pages;
private FrameworkElement resourcesOwner;
private DispatcherObject dispatcherOwner;
private Frame currentPageContainer;
private void ShowPageForward()
{
dispatcherOwner.Dispatcher.Invoke((Action)delegate {
if (currentPageContainer.Content != null)
{
var page = currentPageContainer.Content as Page;
if (page != null)
{
page.Loaded -= NextPage_Loaded;
UnloadPageForward(page);
}
}
else
{
LoadPageForward();
}
});
}
private void UnloadPageForward(Page page)
{
Storyboard sb = (resourcesOwner.FindResource("SlideForwardOut") as Storyboard).Clone();
sb.Completed += StoryboardForward_Completed;
sb.Begin(currentPageContainer);
}
private void StoryboardForward_Completed(object sender, EventArgs e)
{
LoadPageForward();
}
private void LoadPageForward()
{
pages.Peek().Loaded += NextPage_Loaded;
currentPageContainer.Content = pages.Peek();
}
private void NextPage_Loaded(object sender, RoutedEventArgs e)
{
Storyboard sb = resourcesOwner.FindResource("SlideForwardIn") as Storyboard;
sb.Begin(currentPageContainer);
}
}
I'm new with WPF and may be just don't understand some details, so I will be happy if you help me to solve this small but very offensive issue.
Update #1: software versions
OS for development: Windows 10 x64
OS for test: Windows 8.1 x64
VS version: Visual Studio 2017 Community Edition
Application target framework: v.4.5
Since WPF controls have thread affinity it doesn't make much sense to create them on a background thread in most cases.
If you want to wait for 2 seconds before you show the login page, you could either use a DispatcherTimer or wait asynchronously:
public partial class WelcomePage : Page
{
public WelcomePage(MainWindow parent)
{
InitializeComponent();
this.parent = parent;
ShowLoginForm();
}
private MainWindow parent;
private async void ShowLoginForm()
{
await Task.Delay(2000);
this.parent.GoToLoginForm();
}
}
Then you won't need any calls to Dispatcher.Invoke.
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;
}
};
}
I have a page transition ( a control ) in the MainWindow , I have many user control pages , I want to access the page transition in the MainWindow from my user control page ? How do I do that?
I tried :
Story page = new Story();
NavigationService nav = NavigationService.GetNavigationService(this);
// Navigate to the page, using the NavigationService
// if (nav != null)
// {
// nav.Navigate(page);
MainWindow test = new MainWindow();
test.pageTransition1.ShowPage(page);
// }
Application.Current.MainWindow
Using this you can access the MainWindow from any place.
You could find the WpfPageTransitions.PageTransition control like this from the UserControls code behind:
public static WpfPageTransitions.PageTransition FindPageControl(DependencyObject child)
{
DependencyObject parent= VisualTreeHelper.GetParent(child);
if (parent == null) return null;
WpfPageTransitions.PageTransition page = parent as WpfPageTransitions.PageTransition;
if (page != null)
{
return page;
}
else
{
return FindPageControl(parent);
}
}
Then you can use it like this:
this.FindPageControl(this).ShowPage(...);
Create A Method Inside Main Window for Choosing Page Transition
public void ChangePage()
{
pageTransitionControl.ShowPage(new NewData());
}
Then in Child control
private void btnUpdate_Click(object sender,RoutedEventArgs e)
{
MainWindow win = (MainWindow)Window.GetWindow(this);
win.ChangePage();
}