How to switch between pages with Xamarin.Forms.INavigation.InsertPageBefore - c#

I'm using Xamarin Forms and I'm having an issue use InsertPageBefore() method with existing objects of Pages.
Here is my view code:
private FirstPage firstPage;
private SecondPage secondPage = new SecondPage();
private ThirdPage thirdPage = new ThirdPage();
private async void ItemSelectedMethod()
{
var root = App.NavigationPage.Navigation.NavigationStack[0];
if (SelectedItem == Items[0])
{
if (!IsFirstChoose)
{
App.NavigationPage.Navigation.InsertPageBefore(firstPage, root);
await App.NavigationPage.PopToRootAsync(false);
}
}
if (SelectedItem == Items[1])
{
App.NavigationPage.Navigation.InsertPageBefore(secondPage, root);
await App.NavigationPage.PopToRootAsync(false);
}
if (SelectedItem == Items[2])
{
App.NavigationPage.Navigation.InsertPageBefore(thirdPage, root);
await App.NavigationPage.PopToRootAsync(false);
}
IsFirstChoose = false;
rootPageViewModel.IsPresented = false;
}
It's throw exception "System.ArgumentException: 'Cannot insert page which is already in the navigation stack'". How to switch between existing objects of pages? I don't want create new object in InsertPageBefore(). I tried use it code, before call InsertPageBefore():
foreach (var item in App.NavigationPage.Navigation.NavigationStack.ToList())
App.NavigationPage.Navigation.RemovePage(item);
But it's not working... Can anyone help me?

It didn't work with UWP. Here is agly workaround for you but you really need to read how to work with Master-Detail pages.
public partial class App : Application
{
public static RootPage RootPage { get; private set; } //DON'T DO THIS,
//FIND A BETTER WAY
public App()
{
InitializeComponent();
RootPage = new RootPage();
MenuPage menuPage = new MenuPage(RootPage.vm);
RootPage.Master = menuPage;
RootPage.Detail = new NavigationPage(new MainPage());// NavigationPage;
MainPage = RootPage;
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
Then
private async void ItemSelectedMethod()
{
if (SelectedItem == Items[0])
{
App.RootPage.Detail = new NavigationPage(mainPage);
}
if (SelectedItem == Items[1])
{
App.RootPage.Detail = new NavigationPage(secondPage);
}
if (SelectedItem == Items[2])
{
App.RootPage.Detail = new NavigationPage(thirdPage);
}
rootPageViewModel.IsPresented = false;
}

Related

Is my loading icon bug a Xamarin Forms issue or my Code-Behind Issue

I thought that my bug was a Xamarin Forms issue because there was no bug in XF3.4, but it appeared after I upgraded to XF4.4.
Just to make sure, I want to show you guys the code. I have a XAML page with the loading icon:
<ActivityIndicator IsRunning="{Binding Loading}"
IsVisible="{Binding Loading}"
AbsoluteLayout.LayoutFlags="All"
AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.2, 0.2">
<ActivityIndicator.Color>
<OnPlatform x:TypeArguments="Color" iOS="#2499CE" WinPhone="#2499CE" />
</ActivityIndicator.Color>
</ActivityIndicator>
The "Loading" boolean is binded in the page model here:
public class MyLoginWebPageModel : BasePageModel
{
private BrowserOptions _options;
private Action<BrowserResult> _trySetResult;
private BrowserResult _result = new BrowserResult() { ResultType = BrowserResultType.UserCancel };
private Boolean _navPopped = false;
public string StartUrl { get; private set; }
public bool Loading { get; set; } = false; // RIGHT HERE!!!!!!!!!!!!!!!!!!!!!!
public OidcLoginWebPageModel(ICoreDataRepository repository, ILoginProvider loginProvider, ICache cache, IEventTrace trace, IUsageTimer usageTimer, IPlatform platform)
: base(loginProvider, cache, trace, usageTimer, platform){}
public override void Init(object initData)
{
base.Init(initData);
Tuple<BrowserOptions, Action<BrowserResult>> initObject = initData as Tuple<BrowserOptions, Action<BrowserResult>>;
_options = initObject.Item1;
_trySetResult = initObject.Item2;
StartUrl = _options.StartUrl;
}
protected override void OnPageWasPopped(object sender, EventArgs e)
{
base.OnPageWasPopped(sender, e);
_trySetResult(_result);
}
internal async Task OnBrowserNavigated(object sender, WebNavigatedEventArgs e)
{
Loading = false;
if (!(sender is WebView browser))
{
throw new Exception($"Sender is not of type WebView");
}
if (!Uri.TryCreate(e.Url, UriKind.Absolute, out Uri uri))
{
throw new Exception($"Uri creation failed for: {e.Url}");
}
if (string.IsNullOrEmpty(_options.EndUrl))
{
if (uri.LocalPath.ToLowerInvariant() == "/account/logout")
{
_result = new BrowserResult() { ResultType = BrowserResultType.Success };
if (!_navPopped)
{
_navPopped = true;
await PopPageModel();
}
}
}
}
internal async Task OnBrowserNavigating(object sender, WebNavigatingEventArgs e)
{
Loading = true;
if (!(sender is WebView browser))
{
throw new Exception($"Sender is not of type WebView");
}
if (!Uri.TryCreate(e.Url, UriKind.Absolute, out Uri uri))
{
throw new Exception($"Uri creation failed for: {e.Url}");
}
if (string.IsNullOrEmpty(_options.EndUrl) == false)
{
if (uri.AbsoluteUri.StartsWith(_options.EndUrl))
{
_result = new BrowserResult() { ResultType = BrowserResultType.Success, Response = uri.Fragment.Substring(1) };
e.Cancel = true;
if (!_navPopped)
{
_navPopped = true;
Loading = false;
await PopPageModel();
}
}
}
}
}
Is there anything in here that would indicate a loading icon not disappearing at all?
Thanks!
edit: So this is what I'm thinking I need to do.
First I change my boolean situation
private bool Loading = false;
public bool currentlyLoading
{
get { return Loading; }
set
{
currentlyLoading = Loading;
onPropertyChanged();
}
}
Then in the same file I implement the onPropertyChanged() function to.. let the Bindable property in my xaml file know that the property has changed?
Is this a good implementation?
// Option 1
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
// Option 2
protected void OnPropertyChanged(string propertyName)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, new PropertyChangedEventArgs(propertyName));
}
your class (or it's base class) needs to implement INotifyPropertyChanged. Then your Loading property would look something like this
private bool loading = false;
public bool Loading
{
get { return loading; }
set
{
loading = value;
OnPropertyChanged();
}
}
Option 1 is best. But it would be good if you move your property changed logic to BasePageModel.
Try this one:
private bool _loading { get; set; }
public bool Loading { get {return _loading; } set{value = _loading } }
And in your OnBrowserNavigating:
internal async Task OnBrowserNavigating(object sender, WebNavigatingEventArgs e)
{
Loading = true;
if (!(sender is WebView browser))
{
Loading = false;
throw new Exception($"Sender is not of type WebView");
}
if (!Uri.TryCreate(e.Url, UriKind.Absolute, out Uri uri))
{
Loading = false;
throw new Exception($"Uri creation failed for: {e.Url}");
}
if (string.IsNullOrEmpty(_options.EndUrl) == false) //IF THE CONDITION OVER HERE & FOR INNER IF CONDITIONS FAILS, LOADER WASNT SET TO FALSE
{
if (uri.AbsoluteUri.StartsWith(_options.EndUrl))
{
_result = new BrowserResult() { ResultType = BrowserResultType.Success, Response = uri.Fragment.Substring(1) };
e.Cancel = true;
if (!_navPopped)
{
_navPopped = true;
Loading = false;
await PopPageModel();
}
}
}
Loading = false;
}

Navigation with custom renderer of Exoplayer in xamarin

I have a listview in mainpage,when i click listview it goes to 2nd page where it has Exoplayer integration with a custom renderer,so the problem is how to release the player properly,as currently _player is equals to null always ,multiple audios are played,If navigate from first page to 2nd page multiple times.
Here is the code of custom renderer-
public class VideoPlayerRenderer : ViewRenderer<VideoPlayer, SimpleExoPlayerView>, IExoPlayerEventListener
{
private SimpleExoPlayerView _playerView;
private SimpleExoPlayer _player;
private DefaultTrackSelector trackSelector;
DefaultHttpDataSourceFactory defaultHttpDataSourceFactory;
DefaultDataSourceFactory defaultDataSourceFactory ;
ExtractorMediaSource extractorMediaSource ;
public VideoPlayerRenderer(Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> e)
{
base.OnElementChanged(e);
if (_player == null)
{
InitializePlayer();
}
Play();
}
private void InitializePlayer()
{
trackSelector = new DefaultTrackSelector();
_player = ExoPlayerFactory.NewSimpleInstance(Context,trackSelector);
_player.PlayWhenReady = true;
_playerView = new SimpleExoPlayerView(Context) { Player = _player };
SetNativeControl(_playerView);
SetBackgroundColor(Android.Graphics.Color.Green);
}
private void Play()
{
Uri sourceUri = Uri.Parse(Element.SourceUrl);
var mediaUri = Android.Net.Uri.Parse(Element.SourceUrl);
var userAgent = Util.GetUserAgent(Context, "App1");
defaultHttpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
defaultDataSourceFactory = new DefaultDataSourceFactory(Context, null, defaultHttpDataSourceFactory);
extractorMediaSource = new ExtractorMediaSource(mediaUri, defaultDataSourceFactory, new DefaultExtractorsFactory(), null, null);
_player.Prepare(extractorMediaSource);
_player.AddListener(this);
}
In your custom renderer, override OnDetachedFromWindow to stop the player:
protected override void OnDetachedFromWindow()
{
base.OnDetachedFromWindow();
_player.Stop();
_player.Release();
}
Refer: onDetachedFromWindow
Update:
To control the player, you should create a singleton of SimpleExoPlayer , make sure there is only one _player in your entire project:
class ExoPlayerInstance
{
private static SimpleExoPlayer _player;
public static SimpleExoPlayer player
{
get
{
if (_player == null)
{
_player = ExoPlayerFactory.NewSimpleInstance(Application.Context, new DefaultTrackSelector());
_player.PlayWhenReady = true;
}
return _player;
}
}
}
And then in your custom render, remove the InitializePlayer part and use the ExoPlayerInstance:
protected override void OnElementChanged(ElementChangedEventArgs<VideoPlayer> e)
{
base.OnElementChanged(e);
//remove the InitializePlayer part
//if (_player == null)
// InitializePlayer();
_player = ExoPlayerInstance.player;
_playerView = new SimpleExoPlayerView(Context) { Player = _player };
SetNativeControl(_playerView);
if (_player.IsPlayingAd || _player.IsLoading)
{
//maybe something more need to do , stop() here is an example
_player.Stop();
}
Play();
}

How to change TabbedPage Icon when the tab is reselected in Android?

I have an application using Xamarin Forms TabbedPage which has a feature that would allow the user to pause and play a page. Please see the code below.
Shared Code
public partial class MainPage : TabbedPage
{
public MainPage()
{
InitializeComponent();
var homePage = new NavigationPage(new HomePage())
{
Title = "Home",
Icon = "ionicons_2_0_1_home_outline_25.png"
};
var phrasesPage = new NavigationPage(new PhrasesPage())
{
Title = "Play",
Icon = "ionicons_2_0_1_play_outline_25.png"
};
Children.Add(homePage);
Children.Add(phrasesPage);
}
}
In iOS renderer:
public class TabbedPageRenderer : TabbedRenderer
{
private MainPage _page;
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var tabbarController = (UITabBarController)this.ViewController;
if (null != tabbarController)
{
tabbarController.ViewControllerSelected += OnTabBarReselected;
}
}
void OnTabBarReselected(object sender, UITabBarSelectionEventArgs e)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
if (TabBar.SelectedItem.Title == "Play") {
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else {
if (tabs != null) {
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
Android Renderer
public class MyTabbedPageRenderer: TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
if (e.PropertyName == "Renderer")
{
viewPager = (ViewPager)ViewGroup.GetChildAt(0);
tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
setup = true;
ColorStateList colors = null;
if ((int)Build.VERSION.SdkInt >= 23)
{
colors = Resources.GetColorStateList(Resource.Color.icon_tab, Forms.Context.Theme);
}
else
{
colors = Resources.GetColorStateList(Resource.Color.icon_tab);
}
for (int i = 0; i < tabLayout.TabCount; i++)
{
var tab = tabLayout.GetTabAt(i);
var icon = tab.Icon;
if (icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
var tabs = Element as TabbedPage;
var playTab = tabs.Children[4];
var selectedPosition = tab.Position;
if(selectedPosition == 4)
{
if (playTab.Title == "Play")
{
if (tabs != null)
{
playTab.Title = "Pause";
playTab.Icon = "ionicons_2_0_1_pause_outline_22.png";
}
App.pauseCard = false;
}
else
{
if (tabs != null)
{
playTab.Title = "Play";
playTab.Icon = "ionicons_2_0_1_play_outline_25.png";
}
App.pauseCard = true;
}
}
}
}
This is perfectly working in iOS. But somehow in Android only the Title would change but not the Icon. Anyone knows what Im missing or how it should be done? Also, is this possible to be done in the shared code instead of repeating almost exactly the same lines on code in each platform?
You can do it by using the tab that is being passe to you in the OnTabReselected parameters in the TabRenderer.
You can move your whole logic with this object.
This is my whole renderer file (Android):
[assembly: ExportRenderer(typeof(SWTabSelection.MainPage), typeof(SWTabSelection.Droid.MyTabbedPageRenderer))]
namespace SWTabSelection.Droid
{
public class MyTabbedPageRenderer : TabbedPageRenderer, TabLayout.IOnTabSelectedListener
{
private ViewPager viewPager;
private TabLayout tabLayout;
private bool setup;
public MyTabbedPageRenderer() { }
public MyTabbedPageRenderer(Context context) : base(context)
{
//Use this constructor for newest versions of XF saving the context parameter
// in a field so it can be used later replacing the Xamarin.Forms.Forms.Context which is deprecated.
}
protected override void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e)
{
base.OnElementPropertyChanged(sender, e);
if (e.PropertyName == "Renderer")
{
viewPager = (ViewPager)ViewGroup.GetChildAt(0);
tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
setup = true;
ColorStateList colors = GetTabColor();
for (int i = 0; i < tabLayout.TabCount; i++)
{
var tab = tabLayout.GetTabAt(i);
SetTintColor(tab, colors);
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
// To have the logic only on he tab on position 1
if(tab == null || tab.Position != 1)
{
return;
}
if(tab.Text == "Play")
{
tab.SetText("Pause");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_pause_outline_25);
App.pauseCard = false;
}
else
{
tab.SetText("Play");
tab.SetIcon(Resource.Drawable.ionicons_2_0_1_play_outline_25);
App.pauseCard = true;
}
SetTintColor(tab, GetTabColor());
}
void SetTintColor(TabLayout.Tab tab, ColorStateList colors)
{
var icon = tab?.Icon;
if(icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
ColorStateList GetTabColor()
{
return ((int)Build.VERSION.SdkInt >= 23)
? Resources.GetColorStateList(Resource.Color.icon_tab, Forms.Context.Theme)
: Resources.GetColorStateList(Resource.Color.icon_tab);
}
}
}
The only thing that I had with the code above is that the icon was not taking the Tint color so created a function with the same logic you had to set the Tint and I am using it on the Tab Reselection. If you have only one tab in your app you can set a global tint in the Android Theme/Style xml.
Hope this helps.
Custom Renderer is no needed , you can change the Title and Icon directly in Shared code.
Just implement CurrentPageChanged event in TabbedPage
Complete code
public partial class TabbedPage1 : TabbedPage
{
NavigationPage homePage;
NavigationPage phrasesPage;
public TabbedPage1 ()
{
InitializeComponent();
var homePage = new NavigationPage(new Page1())
{
Title = "Home",
Icon = "1.png"
};
var phrasesPage = new NavigationPage (new Page2())
{
Title = "Play",
Icon = "1.png"
};
Children.Add(homePage);
Children.Add(phrasesPage);
this.CurrentPageChanged += (object sender, EventArgs e) => {
var i = this.Children.IndexOf(this.CurrentPage);
if (i == 0)
{
homePage.Title = "HomeChanged";
homePage.Icon = "2.png";
}
else {
phrasesPage.Title = "PlayChanged";
phrasesPage.Icon = "2.png";
}
};
}
}
Result
PS: Make the image files access from a different platform.
iOS - Resources
Android - Resources->drawable
There isn't a way to detect when a tab is reselected in Xamarin.Forms, so we'll have to use custom rederers to detect the logic.
For Android, we'll have to handle two cases: Current Tab Page Changed, and Current Tab Page Reselected.
We'll subscribe to to CurrentPageChanged and in its EventHandler, we'll check to see if the tab selected is PhrasesPage. If it is, we'll update the Icon/Text.
In OnTabReselected, we can check which page is currently selected, and if it's the PhrasesPage, we can update PhrasesPage.Icon and PhrasesPage.Text.
Sample App
https://github.com/brminnick/ChangeTabbedPageIconSample/tree/master
Android Custom Renderer
[assembly: ExportRenderer(typeof(MainPage), typeof(TabbedPageRenderer))]
namespace YourNameSpace
{
public class TabbedPageRenderer : TabbedRenderer, TabLayout.IOnTabSelectedListener
{
//Overloaded Constructor required for Xamarin.Forms v2.5+
public TabbedPageRenderer(Android.Content.Context context) : base(context)
{
}
protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e)
{
base.OnElementChanged(e);
Element.CurrentPageChanged += HandleCurrentPageChanged;
}
void HandleCurrentPageChanged(object sender, EventArgs e)
{
var currentNavigationPage = Element.CurrentPage as NavigationPage;
if (!(currentNavigationPage.RootPage is PhrasesPage))
return;
var tabLayout = (TabLayout)ViewGroup.GetChildAt(1);
for (int i = 0; i < tabLayout.TabCount; i++)
{
var currentTab = tabLayout.GetTabAt(i);
var currentTabText = currentTab.Text;
if (currentTabText.Equals("Play") || currentTabText.Equals("Pause"))
{
Device.BeginInvokeOnMainThread(() => UpdateTab(currentTabText, currentTab, currentNavigationPage));
break;
}
}
}
void TabLayout.IOnTabSelectedListener.OnTabReselected(TabLayout.Tab tab)
{
System.Diagnostics.Debug.WriteLine("Tab Reselected");
var mainPage = Application.Current.MainPage as MainPage;
var currentNavigationPage = mainPage.CurrentPage as NavigationPage;
if(currentNavigationPage.RootPage is PhrasesPage)
Device.BeginInvokeOnMainThread(() => UpdateTab(currentNavigationPage.Title, tab, currentNavigationPage));
}
void UpdateTab(string currentTabText, TabLayout.Tab tab, NavigationPage currentNavigationPage)
{
if (currentTabText.Equals("Puzzle"))
{
tab.SetIcon(IdFromTitle("Settings", ResourceManager.DrawableClass));
currentNavigationPage.Title = "Settings";
}
else
{
tab.SetIcon(IdFromTitle("Puzzle", ResourceManager.DrawableClass));
currentNavigationPage.Title = "Puzzle";
}
}
int IdFromTitle(string title, Type type)
{
string name = System.IO.Path.GetFileNameWithoutExtension(title);
int id = GetId(type, name);
return id;
}
int GetId(Type type, string memberName)
{
object value = type.GetFields().FirstOrDefault(p => p.Name == memberName)?.GetValue(type)
?? type.GetProperties().FirstOrDefault(p => p.Name == memberName)?.GetValue(type);
if (value is int)
return (int)value;
return 0;
}
}
}
I think you are using the custom render for tabbed page customization. For, Android you should refer the icon from Resource.Drawable. Please try with below code snippet in android renderer.
public class CustomTabRenderer: TabbedRenderer
{
private Activity _act;
protected override void OnModelChanged(VisualElement oldModel, VisualElement newModel)
{
base.OnModelChanged(oldModel, newModel);
_act = this.Context as Activity;
}
// You can do the below function anywhere.
public override void OnWindowFocusChanged(bool hasWindowFocus)
{
ActionBar actionBar = _act.ActionBar;
if (actionBar.TabCount > 0)
{
Android.App.ActionBar.Tab tabOne = actionBar.GetTabAt(0);
tabOne.SetIcon(Resource.Drawable.shell);
}
base.OnWindowFocusChanged(hasWindowFocus);
}
}
Refer this also: https://forums.xamarin.com/discussion/17654/tabbedpage-icons-not-visible-android
Try adding this code to OnElementChanged in TabbedPageRenderer
if (e.PropertyName == "Renderer")
{
ViewPager pager = (ViewPager)ViewGroup.GetChildAt(0);
TabLayout layout = (TabLayout)ViewGroup.GetChildAt(1);
for (int i = 0; i < layout.TabCount; i++)
{
var tab = layout.GetTabAt(i);
var icon = tab.Icon;
if (icon != null)
{
icon = Android.Support.V4.Graphics.Drawable.DrawableCompat.Wrap(icon);
Android.Support.V4.Graphics.Drawable.DrawableCompat.SetTintList(icon, colors);
}
}
}
More info here : https://montemagno.com/xamarin-forms-android-selected-and-unselected-tab-colors/

Windows store app navigation

I'm making a Pacman windows store app game. I use win2d library to make animations. I have a problem in navigation between pages.
Here's my main page, it creates new Game.
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
//Game gm = new Game();
}
private void playButton_Click(object sender, RoutedEventArgs e)
{
Game gm = new Game();
}
private void exitButton_Click(object sender, RoutedEventArgs e)
{
Application.Current.Exit();
}
private void resultsButton_Click(object sender, RoutedEventArgs e)
{
}
}
but in Game class when end finishes I have to somehow comeback to my main page. I have tried many ways but they doesn't work for me.
Game class:
public Game()
{
this.InitializeComponent();
Window.Current.Content = this;
}
private void canvas_CreateResources(CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
args.TrackAsyncAction(CreateResourcesAsync(sender).AsAsyncAction());
}
async Task CreateResourcesAsync(CanvasAnimatedControl sender)
{
ghostImages = new List<CanvasBitmap>();
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost1.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost2.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost3.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/ghost4.png")));
ghostImages.Add(await CanvasBitmap.LoadAsync(sender.Device, new Uri("ms-appx:///Assets/Pacman_25.png")));
StartNewGame();
}
private void Canvas_Draw(ICanvasAnimatedControl sender, CanvasAnimatedDrawEventArgs args)
{
Map.drawBorders(args);
using (var session = args.DrawingSession)
{
session.DrawImage(hero.getImage1(), hero.getX(), hero.getY());
for (int i = 0; i < ghostList.ToArray().Length; i++)
{
session.DrawImage(ghostList[i].getImage(), ghostList[i].getX(), ghostList[i].getY());
}
int bestScore = 1, score = 3;
session.DrawText("Rekordas: " + bestScore, Constants.WIDTH / 3 * 1.8f, Constants.HEIGHT + Constants.SHOWINFOSIZE / 2, Windows.UI.Colors.White);
session.DrawText("Rezultatas: " + score, Constants.BLOCKSIZE, Constants.HEIGHT + Constants.SHOWINFOSIZE / 2, Windows.UI.Colors.White);
session.DrawText("Gyvybės: ", Constants.BLOCKSIZE, Constants.HEIGHT + Constants.SHOWINFOSIZE / 1, Windows.UI.Colors.White);
for (int i = 0; i < 3; i++)
session.DrawImage(hero.getImage1(), Constants.BLOCKSIZE + 150 + (Constants.BLOCKSIZE + 5) * i, (int)Constants.HEIGHT + Constants.SHOWINFOSIZE / 1 - Constants.BLOCKSIZE + 5);
}
}
public void GameOver()
{
playing = false;
//Frame.Navigate(typeof(MainPage));
//Dispose();
//this.Dispose();
//var page = new MainPage();
//Window.Current.Content = page;
//MainPage mn = new MainPage();
//if (name == null)
//{
// name = "Student";
//}
//Window.Current.Content = new MainPage();
//mn.UpdateLayout();
}
How can I navigate through pages? Thanks.
Here are some methods that you might find helpful (from a class that I use to wrap navigation logic inside)
//Better made the class a singleton but I've skipped that part to for brifety
public class Navigation
{
public bool CanGoBack
{
get
{
var frame = ((Frame)Window.Current.Content);
return frame.CanGoBack;
}
}
public Type CurrentPageType
{
get
{
var frame = ((Frame)Window.Current.Content);
return frame.CurrentSourcePageType;
}
}
public virtual void GoBack()
{
var frame = ((Frame)Window.Current.Content);
if (frame.CanGoBack)
{
frame.GoBack();
}
}
public virtual void NavigateTo(Type sourcePageType)
{
((Frame)Window.Current.Content).Navigate(sourcePageType);
}
public virtual void NavigateTo(Type sourcePageType, object parameter)
{
((Frame)Window.Current.Content).Navigate(sourcePageType, parameter);
}
public virtual void GoForward()
{
var frame = ((Frame)Window.Current.Content);
if (frame.CanGoForward)
{
frame.GoForward();
}
}
}
You use it like this (if we assume the aforementioned methods reside in a class named Navigation that you have instance of):
//To go to Game page
Navigation.NavigateTo(typeof(Game));
//To go to Main page and pass some arguments
Navigation.NavigateTo(typeof(MainPage), winnerId);
//To go back
Navigation.GoBack();
Addition
You could receive your passed parameters in your views like this:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var receivedParameter = e.Parameter as TheTypeOfThePassedParameter;
}
Additional option to pass data is to create one static or singleton application-wise class (visible from everywhere) containing some values that you want available throughout your app
I suggest you to consider Model View View Model pattern to manage your App's navigation logic and contents. (Channel9 introductive video)
To help you with navigation, you could use MVVM Light library that exposes some useful navigation methods:
In ViewModelLocator.cs you could define a string for every page to be navigated:
static ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
var nav = new NavigationService();
nav.Configure("MainMenu", typeof(MainMenuView));
nav.Configure("About", typeof(AboutView));
nav.Configure("Game", typeof(GameView));
SimpleIoc.Default.Register<INavigationService>(() => nav);
SimpleIoc.Default.Register<MainMenuViewModel>();
SimpleIoc.Default.Register<AboutViewModel>();
SimpleIoc.Default.Register<GameViewModel>();
}
A typical ViewModel could be:
public class GameViewModel : ViewModelBase
{
private INavigationService _navigationService;
public GameViewModel(INavigationService NavigationService)
{
_navigationService = NavigationService;
}
// Then, when you want to expose a navigation command:
private RelayCommand _navigateToMenuCommand;
public RelayCommand NavigateToMenuCommand
{
get
{
return _navigateToMenuCommand
?? (_navigateToMenuCommand = new RelayCommand(
() =>
{
_navigationService.NavigateTo("MainMenu");
}
{
}
}
And .XAML:
<Button Content="Back to Main Menu" Command={Binding GameViewModel} />

Xamarin UISwipeGestureRecognizer Renderer

In Xamarin.Forms I want to be able to swipe left and right to navigate a list of pictures. At the moment I just want to be able to fire an event each time a swipe is detected.
child class to be used in the renderer:
public class LRMasterDetailPage : ContentView
{
}
I have a ContentPage that uses the LRM class like this:
public class ImagePage : ContentPage
{
public ImagePage(Photo photo)
{
_image = new WebImage
{
Url = photo.Url,
Placeholder = "placeHolder2.png"
};
var imageView = new LRMasterDetailPage {
Content = _image
};
this.Content = imageView;
}
}
This is my Renderer:
[assembly:ExportRenderer (typeof(LRMasterDetailPage), typeof(LRMDPRenderer))]
namespace Project.iOS
{
public class LRMDPRenderer : ViewRenderer<LRMasterDetailPage,UIView>
{
UISwipeGestureRecognizer swipe;
protected override void OnElementChanged (ElementChangedEventArgs<LRMasterDetailPage> e)
{
base.OnElementChanged (e);
// Do someting else, init for example
swipe = new UISwipeGestureRecognizer();
this.AddGestureRecognizer (swipe);
if (swipe.Direction == UISwipeGestureRecognizerDirection.Left)
{
UpdateLeft ();
}
if (swipe.Direction == UISwipeGestureRecognizerDirection.Right)
{
UpdateRight ();
}
}
protected override void OnElementPropertyChanged (object sender, System.ComponentModel.PropertyChangedEventArgs e)
{
if (e.PropertyName == "Renderer")
return;
base.OnElementPropertyChanged (sender, e);
}
private void UpdateLeft(){
// 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");
}
private void UpdateRight(){
// same as for left, but flipped
Console.WriteLine ("Right swipe");
}
}
}
When the ContentPage is display the swipe right event is activated, but nothing happens when I try to swipe on top of the image. I am guessing my logic on the renderer is wrong?
After much pain and searching around the web I found the solution.
What you need to do is simple: in the renderer declare and add the gesture. Make sure you create a swipe gesture for right and left each by declaring their direction. from there use a lambda to call the function you want activated for the specific swipe:
Placeholder class for the Swipe Gestures.
public class LRMasterDetailPage : ContentView
{
}
Image Page that holds one image at the time
public class ImagePage : ContentPage
{
//view holding the image
LRMasterDetailPage imageView;
//collection of images using the photo.Url
ObservableCollection<Image> images;
//current image index
int index = 0;
public ImagePage(){
images = new ObservableCollection<Image> ();
imageView = new LRMasterDetailPage {
Content = this.images [index]
};
this.Content = imageView;
}
//Subscribe to the swipeLeft and swipeRight message
protected override void OnAppearing ()
{
base.OnAppearing ();
MessagingCenter.Subscribe <string> (this,"LeftSwipe", (sender) => {
//Do something
if(index < images.Count-1){
index++;
}
imageView.Content = this.images[index];
});
MessagingCenter.Subscribe <string> (this, "RightSwipe", (sender) => {
//Do something
if(index > 0){
index--;
}
imageView.Content = this.images[index];
});
}
protected override void OnDisappearing()
{
base.OnDisappearing();
//this._image = null;
images = null;
MessagingCenter.Unsubscribe<string>(this,"LeftSwipe");
MessagingCenter.Unsubscribe<string>(this, "RightSwipe");
MessagingCenter.Unsubscribe<string>(this, "LongPress");
//GC.Collect();
}
}
Renderer for LRMasterDetailPage
[assembly:ExportRenderer (typeof(LRMasterDetailPage), typeof(LRMDPRenderer))]
namespace Manager.iOS
{
public class LRMDPRenderer : ViewRenderer<LRMasterDetailPage,UIView>
{
UILongPressGestureRecognizer longPressGestureRecognizer;
UIPinchGestureRecognizer pinchGestureRecognizer;
//UIPanGestureRecognizer panGestureRecognizer;
UISwipeGestureRecognizer swipeRightGestureRecognizer;
UISwipeGestureRecognizer swipeLeftGestureRecognizer;
UIRotationGestureRecognizer rotationGestureRecognizer;
protected override void OnElementChanged (ElementChangedEventArgs<LRMasterDetailPage> e)
{
base.OnElementChanged (e);
longPressGestureRecognizer = new UILongPressGestureRecognizer (() => Console.WriteLine ("Long Press"));
pinchGestureRecognizer = new UIPinchGestureRecognizer (() => Console.WriteLine ("Pinch"));
//panGestureRecognizer = new UIPanGestureRecognizer (() => Console.WriteLine ("Pan"));
swipeRightGestureRecognizer = new UISwipeGestureRecognizer ( () => UpdateRight()){Direction = UISwipeGestureRecognizerDirection.Right};
swipeLeftGestureRecognizer = new UISwipeGestureRecognizer ( () => UpdateLeft()){Direction = UISwipeGestureRecognizerDirection.Left};
rotationGestureRecognizer = new UIRotationGestureRecognizer (() => Console.WriteLine ("Rotation"));
if (e.NewElement == null) {
if (longPressGestureRecognizer != null) {
this.RemoveGestureRecognizer (longPressGestureRecognizer);
}
if (pinchGestureRecognizer != null) {
this.RemoveGestureRecognizer (pinchGestureRecognizer);
}
/*
if (panGestureRecognizer != null) {
this.RemoveGestureRecognizer (panGestureRecognizer);
}
*/
if (swipeRightGestureRecognizer != null) {
this.RemoveGestureRecognizer (swipeRightGestureRecognizer);
}
if (swipeLeftGestureRecognizer != null) {
this.RemoveGestureRecognizer (swipeLeftGestureRecognizer);
}
if (rotationGestureRecognizer != null) {
this.RemoveGestureRecognizer (rotationGestureRecognizer);
}
}
if (e.OldElement == null) {
this.AddGestureRecognizer (longPressGestureRecognizer);
this.AddGestureRecognizer (pinchGestureRecognizer);
//this.AddGestureRecognizer (panGestureRecognizer);
this.AddGestureRecognizer (swipeRightGestureRecognizer);
this.AddGestureRecognizer (swipeLeftGestureRecognizer);
this.AddGestureRecognizer (rotationGestureRecognizer);
}
}
private void UpdateLeft(){
MessagingCenter.Send ("Swiped to the left", "LeftSwipe");
}
private void UpdateRight(){
// same as for left, but flipped
MessagingCenter.Send ("Swiped to the Right", "RightSwipe");
}
}
}

Categories