Prevent users from swiping further than expected using CarouselPage Xamarin.Forms - c#

I am struggling to find a solution to this issue with CarouselPages:
Basically, users are able to swipe further than what I wish them to, and this shows a white page behind the current one being displayed. Everything works perfectly fine on Android, but not on iOS.
I have tried this: https://forums.xamarin.com/discussion/72031/is-there-a-way-to-disable-swipe-gesture-when-using-carouselpage but it completely disables the animation when changing. I still want the animation to be there, but not have the swipe enabled.
Thank you in advance.

Why not just remove all Pages but one as long as swiping should be disabled?
using System.Collections.Generic;
using Xamarin.Forms;
namespace MyNamespace
{
public class MyClass : CarouselPage
{
protected override void OnAppearing()
{
base.OnAppearing();
foreach (var child in Children)
{
if (child != CurrentPage)
Children.Remove(child);
}
}
public void DisplayPage(ContentPage page)
{
Children.Clear();
Children.Add(page);
}
}
}
From: Is there a way to disable swipe gesture when using Carouselpage

In response to #Héctor Manuel Martínez Durán's answer:
Foreach loop will throw exception Collection was modified; enumeration operation may not execute - to fix:
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] != CurrentPage)
Children.Remove(Children[i]);
}
Original:
protected override void OnAppearing()
{
base.OnAppearing();
foreach (var child in Children)
{
if (child != CurrentPage)
Children.Remove(child);
}
}
Sample implementation:
public class MyPage : CarouselPage
{
ContentPage ChildPage1 = new ContentPage();
ContentPage ChildPage2 = new ContentPage();
public MyPage()
{
Padding = new Thickness(0, 30, 0, 0);
Children.Add(ChildPage1);
Children.Add(ChildPage2);
CurrentPage = ChildPage1;
}
protected override void OnAppearing()
{
base.OnAppearing();
for (int i = 0; i < Children.Count; i++)
{
if (Children[i] != CurrentPage)
Children.Remove(Children[i]);
}
}
public void DisplayPage(ContentPage page)
{
Children.Clear();
Children.Add(page);
}
}
NOTE: Attempted to submit as an edit, results below
Jo*** review: Reject
This edit does not make the post even a little bit easier to read, easier to find, more accurate or more accessible. Changes are either completely superfluous or actively harm readability.
Ho*** review: Reject
This edit was intended to address the author of the post and makes no sense as an edit. It should have been written as a comment or an answer.

List<ContentPage> pageList;
public MainPage ()
{
InitializeComponent ();
}
protected override void OnAppearing()
{
base.OnAppearing();
pageList = Children.ToList();
foreach (var child in Children.ToList())
{
if (child != CurrentPage)
{
Children.Remove(child);
}
}
}
private void Button_Clicked(object sender, System.EventArgs e)
{
Children.Add(pageList[1]);
CurrentPage = Children[1];
Children.RemoveAt(0);
}

I came across this self-contained custom solution. Single code file and simple usage.
Article here: https://www.roccacreative.co.uk/fyi/xamarin-forms-manual-carousel
Code: https://github.com/roccacreative/XamarinForms-Carousel

Related

Xamarin forms ListView keep populating data from List<object> instead of simply display the objet once

I keep encountering this problem no matter what :
The List<Object> listArticles is simply keep adding data everytime the page appears ,it keep populating the data over and over again instead of simply display them once
I have tried to declare a tempList everytime the function is called , but no success
List<PlanDefinition> tempListArticles = new List<PlanDefinition>(listArticles)
How to encouter this problem?
async protected override void OnAppearing()
{
listArticles= articleView.getArticlesFromPlan();
PopulateOrderLists(listArticles);
base.OnAppearing();
}
protected override void OnDisappearing()
{
base.OnDisappearing();
}
private void PopulateOrderLists(List<PlanDefinition> listArticles)
{
List<PlanDefinition> tempListArticles = new List<PlanDefinition>(listArticles);
OrderTemplate prod = new OrderTemplate();
for (var i = 0; i < tempListArticles.Count; i++)
{
prod = new OrderTemplate();
prod.BindingContext = tempListArticles[i].Name;
OrderInformation.Children.Add(prod);
}
}
Only call your PopulateOrderLists once, assumably when the listArticles has not been populated, but adjust it to your requirements of when you want to replace the current listArticles or add to it...
Example:
async protected override void OnAppearing()
{
if (listArticles != null && listArticles.Count > 0)
{
listArticles= articleView.getArticlesFromPlan();
PopulateOrderLists(listArticles);
}
base.OnAppearing();
}

Unable to refresh a CalendarView with UpdateLayout() [UWP]

In my UWP app, I'm trying to refresh the density bars on a calendarview if the user clicks a button, the problem is that, although calendarView.UpdateLayout(); should re-run the CalendarViewDayItemChanging event, it runs only when the CalendarView is loaded the first time. Am I doing something wrong?
public MainPage()
{
this.InitializeComponent();
densityColors.Add(Colors.Green);
}
private void CalendarView_CalendarViewDayItemChanging(CalendarView sender, CalendarViewDayItemChangingEventArgs args)
{
item = args.Item;
if (item < DateTimeOffset.Now)
{
item.SetDensityColors(densityColors);
}
}
private void Button_Click(object sender, RoutedEventArgs e)
{
densityColors[0]=Colors.Blue;
calendarView.UpdateLayout();
}
UpdateLayout won't do anything here since there's no layout update and CalendarViewDayItemChanging is only triggered when you move to another calendar view.
In your case, you just need to manually locate all the CalendarViewDayItems and update their colors on the existing view.
First, create a little Children extension methods to help retrieve all the children of the same type.
public static class Helper
{
public static List<FrameworkElement> Children(this DependencyObject parent)
{
var list = new List<FrameworkElement>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child is FrameworkElement)
{
list.Add(child as FrameworkElement);
}
list.AddRange(Children(child));
}
return list;
}
}
Then, simply call it in your button click handler to get all CalendarViewDayItems, loop them through and set each's color accordingly.
var dayItems = calendarView.Children().OfType<CalendarViewDayItem>();
foreach (var dayItem in dayItems)
{
dayItem.SetDensityColors(densityColors);
}
Note you don't need to worry about new CalendarViewDayItems coming into view as they will be handled by your CalendarView_CalendarViewDayItemChanging callback.

Getting NullReferenceException unless I expand base class object while debugging

First off, I'm using Xamarin Studio 6.1.3 in case that makes any difference.
I'm creating a simple app and want a login form to appear as a sheet. I followed the Xamarin tutorial for creating sheets (https://developer.xamarin.com/guides/mac/user-interface/working-with-dialogs/#Creating_a_Custom_Sheet) but am running into an issue.
Per the tutorial, I have created a class:
using System;
using Foundation;
using AppKit;
namespace SampleProject
{
public partial class UserLoginController : NSViewController
{
private NSViewController _presentor;
public string Username
{
get { return TxtUsername.StringValue; }
set { TxtUsername.StringValue = value; }
}
public string Password
{
get { return TxtPassword.StringValue; }
set { TxtPassword.StringValue = value; }
}
public NSViewController Presentor
{
get { return _presentor; }
set { _presentor = value; }
}
public UserLoginController(IntPtr handle) : base(handle)
{
}
private void CloseDialog()
{
Presentor.DismissViewController(this);
}
partial void BtnCancelClick(NSObject sender)
{
RaiseDialogCanceled();
CloseDialog();
}
partial void BtnLoginClick(NSObject sender)
{
RaiseDialogAccepted();
CloseDialog();
}
public EventHandler DialogAccepted;
internal void RaiseDialogAccepted()
{
if (this.DialogAccepted != null)
this.DialogAccepted(this, EventArgs.Empty);
}
public EventHandler DialogCanceled;
internal void RaiseDialogCanceled()
{
if (this.DialogCanceled != null)
this.DialogCanceled(this, EventArgs.Empty);
}
}
}
And I have added an override for PrepareForSegue in my ViewController class:
public override void PrepareForSegue(NSStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
switch (segue.Identifier)
{
case "UserLoginSegue":
UserLoginController loginSheet = segue.DestinationController as UserLoginController;
loginSheet.Username = ""; //This line throws NullReferenceException unless I set a breakpoint and expand loginSheet.Base before allowing this line to execute.
loginSheet.Password = "";
loginSheet.Presentor = this;
loginSheet.DialogAccepted += (object s, EventArgs e) => { Console.WriteLine("OK Clicked"); };
loginSheet.DialogCanceled += (object s, EventArgs e) => { Console.WriteLine("Cancel Clicked"); };
break;
}
}
See the comment in the above code block. I basically set a breakpoint on that line and when it triggers, I inspect the loginSheet object. If I expand the Base object to inspect it and then continue execution, everything works as expected. If I don't, I get a NullReferenceException whenever code tries to access any fields/properties/methods in the UserLoginController class.
I am completely baffled as to why this is happening. I set a breakpoint in the constructor of UserLoginController and verified it is being called with a handle and that the base constructor should be called as well.
I've read through the tutorial several times and don't see anything that I'm missing. Can't seem to find anybody else having the same problem.
My ultimate question is: What can I do to make the code work as expected?
For the sake of learning (which may shed light on the problem): What is going on behind the scenes when I inspect the base object of my UserLoginController class while debugging?
When you expand an object in the debugger, all properties not marked up with certain attributes are read via reflection so that we can display them in the IDE.
Maybe one of those properties has a side effect? You should be able to reproduce the effect using reflection, then bisect the property list to see who's effecting your behavior.
The solution turned out to be that I need to check the View property of my UserLoginController.
I added the following line:
var theView = loginSheet.View;
and everything works as expected. I have yet to dig into the View property to see what it is doing behind the scenes that makes everything work.
Here is the modified, working PrepareForSegue override method:
public override void PrepareForSegue(NSStoryboardSegue segue, NSObject sender)
{
base.PrepareForSegue(segue, sender);
switch (segue.Identifier)
{
case "UserLoginSegue":
UserLoginController loginSheet = segue.DestinationController as UserLoginController;
var theView = loginSheet.View;
loginSheet.Username = "";
loginSheet.Password = "";
loginSheet.Presentor = this;
loginSheet.DialogAccepted += (object s, EventArgs e) => { Console.WriteLine("OK Clicked"); };
loginSheet.DialogCanceled += (object s, EventArgs e) => { Console.WriteLine("Cancel Clicked"); };
break;
}
}

How to remove or hide Toolbar item in specific page error: System.IndexOutOfRangeException: Index was outside the bounds of the array

I am trying to Remove() or Clear() ToolbarItems. Here is my code where I am creating ToolbarItem in MainPage.cs
public partial class MainPage : MasterDetailPage
{
public ToolbarItem cCounter = new ToolbarItem() { Icon = "picture.png" };
public ToolbarItem pPo = new ToolbarItem() { Text = "-" };
public MainPage()
{
InitializeComponent();
if (Device.OS == TargetPlatform.iOS)
{
provider.Clicked += iOS_Ppo_Clicked;
ToolbarItems.Add(cCounter);
ToolbarItems.Add(pPo);
}
}
private void iOS_Ppo_Clicked(object sender, EventArgs e)
{
OpenWindow();
}
public async void OpenWindow()
{
if (await Common.WindowComands.CanOpenWindow<PPoPage>(Detail))
{
page = new PPoPage(Page3.Allproviders);
this.ToolbarItems.Clear(); // here I am getting error:Index was outside the bounds of the array
page.OnSelected += Page_OnSelected;
await Detail.Navigation.PushAsync(page, false);
}
}
}
Edit: when I included this.ToolbarItems.Clear(); in OpenWindow
method which initialise that another page opens and it works! Cleans
all toolbar items but unfortunately shows this error:
System.IndexOutOfRangeException: Index was outside the bounds of the array.
This items should disappear only for iOS as you see.
Here is my page class where I would like to Remove() these ToolbarItems:
public partial class PPoPage : ContentPage
{
public MainPage main { get; set; }
private List<PPo> Pro;
public PPoPage(List<PPo> po)
{
InitializeComponent();
if (Device.OS == TargetPlatform.iOS)
{
Pro = po;
CreateLayout();
// HERE I WANT TO REMOVE TOOLBARITEMS FOR THIS PAGE
this.ToolbarItem.Remove(main.cCounter); // here there is error
this.ToolbarItems.Clear(); // this also doesn't work, because toolbar items still exist after this initialization.
}
}
}
In this class I tried both approaches, but none work. Thank you for answers or suggestions.
I had task to change visibility of toolbar item and I created base content page with wrapper around ToolbarItems collection. My sample could help you:
sample on git
some "tutorial" that I've posted on CodeReview
First of all I really want to thank for all answers and suggestions I tried to implement both answers on my solution, but unfortunately unsuccessfully. Main reason that I wasn't focusing on class where I am implemented whole iOS toolbar and it was on my iOS project.
On iOS toolbar I just wrote simple if statement:
var currentPage = ((NavigationPage)Element).CurrentPage;
if (currentPage != null && currentPage is ContentPage)
{
TopViewController.NavigationItem.RightBarButtonItems = new UIBarButtonItem[0];
return;
}
The problem here is that PPoPage is a different page instance to MainPage, and thus contains its' own ToolbarItems collection.
Try main.ToolbarItems.Clear() in place of this.ToolbarItems.Clear(), assuming the main property has been initialised, or you can access the top-level current page with Xamarin.Forms.NavigationPage.CurrentPage.

Setting the Focus to an Entry in Xamarin.Forms

This is just a simplified example, but I'm trying to set this up so that when I open up this page in my Application, the first thing that happens is the keyboard pops up ready for the user to type in their response to an Entry field.
var namelabel = new Label { Text = "What is your name?" };
var nameentry = new Entry { Placeholder = "Type here..." };
var colorlabel = new Label { Text = "What's your fav color?" };
var colorentry = new Entry { Placeholder = "Type here..." };
Content = new StackLayout {
Spacing = 15,
Children = { namelabel, nameentry, colorlabel, colorentry }
};
How can I set the focus of the page to the first entry? And additionally, after the user has put in their first entry, how could I set this up so that the user could press a "Next" Button on the Keyboard (or something along those lines) and the app will take them to fill in the second entry?
Use the Focus method
nameentry.Focus();
If you want the focus to be set when your page appears, you should probably do this in the OnAppearing method
protected override void OnAppearing ()
{
base.OnAppearing ();
nameentry.Focus();
}
In one of my projects I did something like this. Please try the following example:
public class EntryFocusBehavior : Behavior<Entry>
{
public string NextFocusElementName { get; set; }
protected override void OnAttachedTo(Entry bindable)
{
base.OnAttachedTo(bindable);
bindable.Completed += Bindable_Completed;
}
protected override void OnDetachingFrom(Entry bindable)
{
bindable.Completed -= Bindable_Completed;
base.OnDetachingFrom(bindable);
}
private void Bindable_Completed(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(NextFocusElementName))
return;
var parent = ((Entry)sender).Parent;
while (parent != null)
{
var nextFocusElement = parent.FindByName<Entry>(NextFocusElementName);
if (nextFocusElement != null)
{
nextFocusElement.Focus();
break;
}
else
{
parent = parent.Parent;
}
}
}
}
And then XAML:
!!! Please let me know if I made a mistake in the code.
Just inside OnAppearing(), add the following code,
protected async override void OnAppearing()
{
await Task.Run(async () =>
{
await Task.Delay(100);
Device.BeginInvokeOnMainThread(async () =>
{
txtName.Focus();
});
});
}
Note: txtName is the reference name of your Entry Field.
Focus() needs to have a visible page and visible control to focus on.
The issue in my case was that is is necessary that OnAppearing has to exit before a page is shown / visible. What helped in my case is to wait for page visibility in a different thread and then set the focus in the main (UI) thread:
protected override void OnAppearing()
{
base.OnAppearing();
Task.Run(() =>
{
while (!IsVisible)
{
Debug.WriteLine("Page not visible, waiting...");
Task.Delay(50).Wait();
}
Device.BeginInvokeOnMainThread(() =>
{
bool gotFocus = Entry.Focus();
if (!gotFocus)
Debug.WriteLine("Could not set focus.");
});
});
}
So long as the element to focus on is the topmost element, this should work. I place this in the OnAppearing method.
base.OnAppearing();
Entry entry = this.FindByName<Entry>("NameOfEntryElement");
entry.Focus();
The source of this info is here:
https://forums.xamarin.com/discussion/100354/entry-focus-not-working-for-android
There is further discussion in the article about timing issues.
I know this is an old thread but this might work for someone as it worked for me.
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(500);
await Task.Run(() =>
{
entryname.Focus();
});
}
It's possible that Focus or RequestFocus because your control didn't load again. You can override onAppearing but even if you use that, it may possible that didn't work because the control is not set again.
So you can use onAppearing, but for the fist apparing you may use Xamarin community toolkit. And LifeCycleEffect
<Entry x:Name="myEntry">
<Entry.Effects>
<xct:LifecycleEffect Loaded="LifeCycleEffect_Loaded" />
</Entry.Effects>
</Entry>
Here in C#
void LifeCycleEffect_Loaded(object? sender, EventArgs e)
{
if ( sender is Entry && (sender as Entry ).Name != null && (sender as Entry).Name.Equals("myEntry") )
// myEntry.Focus() or myEntry.RequestFocus() or (sender as Entry).Focus()
}
I suggest you to take notes from this link
https://learn.microsoft.com/en-us/xamarin/community-toolkit/effects/lifecycleeffect
Can anyone explain to me why this doesn't work
protected override void OnAppearing()
{
base.OnAppearing();
CustomerNameEntry.Focus();
}
But this does (adding async and Task.Delay(1))
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(1);
CustomerNameEntry.Focus();
}
I'd rather not add this clutter, it seems hacky but it's the only way I've been able to get it to work (I've also tried invoking on main thread, to no avail).
The simple one did not work for me:
protected override void OnAppearing()
{
MyEntry.Focus();
}
I had to wait an unspecified amount of time, but did not want to wait longer than necessary. Also, I didn't want to pollute OnAppearing() with delay code, so implemented a helper with a Focus methdo that works:
using System.Threading.Tasks;
using Xamarin.Forms;
namespace MyApp.Xmrn.Views.Helpers
{
internal static class ViewHelper
{
// Disable the warning about a non-awaited async, as we use it as
// fire and forget and return from the method immediately.
#pragma warning disable 1998, 4014
/// <summary>
/// Entry.Focus replacement, that tries to focus a control
/// multiple times until it succeeds.
/// </summary>
/// <param name="entry">Entry control to be focused.</param>
internal static async Task Focus(Entry entry)
{
Task.Run(async () =>
{
int threshold = 20;
while (!entry.Focus() && threshold-- > 0)
await Task.Delay(50);
});
}
#pragma warning restore 1998, 4014
}
}
And then to use it
protected override async void OnAppearing()
{
base.OnAppearing();
await ViewHelper.Focus(MyEntry);
}
protected override void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
base.OnPropertyChanged(propertyName);
if (propertyName == "Renderer")
{
myEntry.Focus();
}
}
This worked perfectly for me :D
protected override async void OnAppearing() {
base.OnAppearing();
while(!passwordInput.Focus()) { await Task.Delay(50); }
}

Categories