Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

Adding pages programmatically to CarouselPage

FotalFotal Member ✭✭✭

Hello everybody!

_I realized the crutch! It works, but I would like to know better approaches to this task. _

I have Months that I want to display in CarouselPage. Here are the problems I faced:
1. At the start, display the second page, the first page on the left, and the third page on the right;
2. Assign a unique identifier to each page (in my case it was the date of the first day of each month);
3. Add pages if user swipe left or right;
4. Add ActivityIndicator to the entire CarouselPage (I added it in templates for each page);
5. Translate CarouselPage events into commands, I get an exception if I use Convac.Behaviors or my own EventToCommandBehavior class.

Month Carousel Page Xaml:

<?xml version="1.0" encoding="utf-8" ?>
<base:CarouselBasePage
    x:Class="MDOSchedule.UI.Pages.AllJobs.CarouselAllJobsWeekPage"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:base="clr-namespace:MDOSchedule.UI.Pages.Base;assembly=MDOSchedule"
    xmlns:templates="clr-namespace:MDOSchedule.UI.Templates;assembly=MDOSchedule"
    xmlns:week="clr-namespace:MDOSchedule.UI.Views.Week;assembly=MDOSchedule"

    x:Name="This"
    Title="{Binding CurrentDate, StringFormat='{0:MMMM yyyy}'}"
    BindingContextChanged="CarouselAllJobsWeekPage_OnBindingContextChanged"
    CurrentPageChanged="CarouselAllJobsWeekPage_OnCurrentPageChanged"
    ItemsSource="{Binding Weeks}"
    PagesChanged="CarouselAllJobsWeekPage_OnPagesChanged">

    <CarouselPage.ToolbarItems>
        <ToolbarItem
            Command="{Binding RefreshItemsCommand}"
            Icon="ic_refresh.png"
            Order="Primary" />
    </CarouselPage.ToolbarItems>

    <CarouselPage.ItemTemplate>
        <DataTemplate>
            <ContentPage Title="{Binding DateOfFirstDayOfWeek}">

                <AbsoluteLayout>
                    <week:ScheduleWeekView
                        AbsoluteLayout.LayoutBounds="0,0,1,1"
                        AbsoluteLayout.LayoutFlags="All"
                        ItemTappedCommand="{Binding ItemTappedCommand}"
                        WeekItems="{Binding Days}" />

                    <templates:ActivityIndicatorTemplate BindingContext="{Binding Source={x:Reference This}, Path=BindingContext}" />
                </AbsoluteLayout>
            </ContentPage>
        </DataTemplate>
    </CarouselPage.ItemTemplate>

</base:CarouselBasePage>

Month Carousel Page Xaml.CS:

    public partial class CarouselAllJobsMonthPage : CarouselBasePage
        {
            #region Private Fields

            private CarouselAllJobsMonthViewModel _viewModel;

            private bool _isInitialized;

            #endregion


            #region Init

            public CarouselAllJobsMonthPage()
            {
                InitializeComponent();
            }

            #endregion


            #region Events

            private void CarouselAllJobsMonthPage_OnBindingContextChanged(object sender, EventArgs e)
            {
                _viewModel = BindingContext as CarouselAllJobsMonthViewModel;
            }

            private void CarouselAllJobsMonthPage_OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (!_isInitialized
                    && Children.Count > 2)
                {
                    CurrentPage = this.Children[1];
                    _isInitialized = true;
                }
            }

            private void CarouselAllJobsMonthPage_OnCurrentPageChanged(object sender, EventArgs e)
            {
                _viewModel.CurrentPageChangedCommand.Execute(this.CurrentPage);
            }

            #endregion
        }

My Carousel View Model:

public class CarouselAllJobsMonthViewModel : BaseViewModel
    {
        // Fields

        #region Public Fields

        public ObservableCollection<Month> Months
        {
            get => Get<ObservableCollection<Month>>();
            set => Set(value);
        }   

        public DateTime CurrentDate
        {
            get => Get<DateTime>();
            set => Set(value);
        }

        #endregion


        #region Private Fields

        private DateTime _previousDate;
        private int _pagesCount = 3;

        private int _pageIndex = 1;

        #endregion


        // Methods  

        #region Commands

        public ICommand RefreshItemsCommand =>
            new Command(async () => await ShowLoading(RefreshItems));

        public ICommand ItemTappedCommand => 
            new Command<DayInfo>(DayTapped);    

        public ICommand CurrentPageChangedCommand => 
            new Command<ContentPage>(async page => await LoadNextPage(page));


        private async Task RefreshItems()
        {
            Months[_pageIndex].Weeks = GetMonth(CurrentDate).Weeks;

            await Task.Delay(1500);
        }

        private void DayTapped(DayInfo obj)
        {

        }

        private async Task LoadNextPage(ContentPage page)
        {
            if (!DateTime.TryParse(page?.Title,
                                   DateTimeFormatInfo.InvariantInfo,
                                   DateTimeStyles.None,
                                   out DateTime newDate))
                return;

            if (newDate.DateEqualsByDay(CurrentDate))
                return;

            await ShowLoading(async () =>
            {
                // Calculate current position and Time
                _previousDate = this.CurrentDate;
                this.CurrentDate = newDate;

                _pageIndex += newDate < _previousDate ? -1 : 1;

                // Add to the head
                if (newDate.DateEqualsByDay(Months[0].FirstDayOfMonth))
                {
                    Months.Insert(0, GetMonth(CurrentDate.AddMonths(-1)));
                    _pagesCount++;
                    await Task.Delay(1500);
                }
                // Add to the tail      
                else if (newDate.DateEqualsByDay(Months[_pagesCount - 1].FirstDayOfMonth))
                {
                    Months.Add(GetMonth(CurrentDate.AddMonths(1)));
                    _pagesCount++;
                    await Task.Delay(1500);
                }
            });
        }

        #endregion


        #region Init



        #endregion


        #region Override Methods

        public override async Task OnPageAppearing()
        {
            await ShowLoading(async () =>
            {
                Months = new ObservableCollection<Month>
                {
                    GetMonth(DateTime.Now.AddMonths(-1)),
                    GetMonth(DateTime.Now),
                    GetMonth(DateTime.Now.AddMonths(1)),
                };

                CurrentDate = Months[0].FirstDayOfMonth;
            });
        }

        #endregion


        #region Private Methods 


        #endregion


        #region Test

        private Month GetMonth(DateTime monthTime)
        {
            var weeks = new List<Week>();

            DateTime firstDayOfMonth = new DateTime(monthTime.Year, monthTime.Month, 1);

            var lastMondayOfPreviousMonth = firstDayOfMonth;
            while (lastMondayOfPreviousMonth.DayOfWeek != DayOfWeek.Monday)
                lastMondayOfPreviousMonth = lastMondayOfPreviousMonth.AddDays(-1);

            var currentDate = lastMondayOfPreviousMonth;
            for (int i = 0; i < 6; i++)
            {
                var week = new Week { Days = GetDays(currentDate) };
                weeks.Add(week);
                currentDate = currentDate.AddDays(7);
            }

            return new Month
            {
                FirstDayOfMonth = firstDayOfMonth,
                Weeks = weeks,
                ItemTappedCommand = this.ItemTappedCommand
            };
        }

        private readonly Random _random = new Random();

        private List<DayInfo> GetDays(DateTime firstDay)
        {
            var days = new List<DayInfo>();
            for (int i = 0; i < 7; i++)
            {
                var jobs = new List<JobObject>();

                if (i != 2)
                    for (int j = 0; j < 10; j++)
                    {
                        jobs.Add(new JobObject()
                        {
                            Color = (j & _random.Next(3)) == 1 ? "#42f47d" : "#ff6677",
                            JobId = _random.Next(70),
                            Monteurs = new List<MonteurObject>()
                            {
                                new MonteurObject()
                                {
                                    TeamName = "Tax"
                                }
                            }
                        });
                    }
                days.Add(new DayInfo(firstDay.AddDays(i), jobs));
            }

            return days;
        }

        #endregion
    }

Answers

  • FotalFotal Member ✭✭✭

    await Task.Delay(1500) just to demonstrate the load

  • PoojaMorePoojaMore Member ✭✭

    @Fotal
    Hi,
    my requirement same as yours. iNeed to create the Calender in Xamarin forms with Week, Days,...etc. can mi send your's code.

  • FotalFotal Member ✭✭✭
    @Pari, Ok, I will help you later, today
  • FotalFotal Member ✭✭✭

    @Pari I cannot share all the code of project. I will describe the construction of the page for the month

    MonthPage.xaml.cs. Where is CarouselBasePage similar to CarouselPage. I wrote this code only to handle the event CarouselPage.OnPageChanging, now I understand that I could use MessagingCentre and it would be much better.

    public partial class AllJobsMonthPage : CarouselBasePage
        {
    
            #region Private Fields
    
            private AllJobsMonthViewModel _viewModel;
    
            private bool _isInitialized;
    
            #endregion
    
    
            #region Init
    
            public AllJobsMonthPage()
            {
                InitializeComponent();
            }
    
            #endregion
    
    
            #region Events
    
            private void AllJobsMonthPage_OnBindingContextChanged(object sender, EventArgs e)
            {
                _viewModel = BindingContext as AllJobsMonthViewModel;
            }
    
            private void AllJobsMonthPage_OnPagesChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                if (!_isInitialized
                    && Children.Count > 2)
                {
                    CurrentPage = Children[1];
                    _isInitialized = true;
                }
            }
    
            private void AllJobsMonthPage_OnCurrentPageChanged(object sender, EventArgs e)
            {
                _viewModel.CurrentPageChangedCommand.Execute(CurrentPage);
            }
    
    
            #endregion
        }
    

    MonthPage.xaml. Here I set ItemSource property from my ViewModel

    <base:CarouselBasePage
        x:Class="MDOSchedule.UI.Pages.Jobs.AllJobs.AllJobsMonthPage"
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:base="clr-namespace:MDOSchedule.UI.Pages.Base;assembly=MDOSchedule"
        x:Name="This"
        Title="{Binding CurrentDate, StringFormat='{0:MMMM yyyy}'}"
        BackgroundColor="White"
        BindingContextChanged="AllJobsMonthPage_OnBindingContextChanged"
        CurrentPageChanged="AllJobsMonthPage_OnCurrentPageChanged"
        ItemTemplate="{StaticResource MonthViewPageTemplate}"
        ItemsSource="{Binding Months}"
        PagesChanged="AllJobsMonthPage_OnPagesChanged">
    
        <CarouselPage.ToolbarItems>
            <ToolbarItem
                Command="{Binding RefreshItemsCommand}"
                Icon="ic_refresh.png"
                Order="Primary" />
        </CarouselPage.ToolbarItems>
    
    </base:CarouselBasePage>
    

    MonthViewModel

    #region Public Fields
    
            public ObservableCollection<MonthInfo> Months
            {
                get => Get<ObservableCollection<MonthInfo>>();
                set => Set(value);
            }
    
            public DateTime CurrentDate
            {
                get => Get<DateTime>();
                set => Set(value);
            }
    
            public int PageIndex
            {
                get => Get<int>();
                private set
                {
                    Set(value);
                    CurrentMonth = Months[value];
                }
            }
    
            public MonthInfo CurrentMonth
            {
                get => Get<MonthInfo>();
                private set => Set(value);
            }
    
            public new bool IsBusy
            {
                get => CurrentMonth.IsBusy;
                set => CurrentMonth.IsBusy = value;
            }
    
            #endregion
    
    
            #region Private Fields
    
            private DateTime _previousDate;
            private int _pagesCount = 3;
    
            protected abstract IMonthService MonthService { get; }
    
            #endregion
    
    
            #region Commands
    
            public ICommand CurrentPageChangedCommand =>
                new Command<ContentPage>(async page => await LoadNextPage(page));
    
    
            protected virtual async Task LoadNextPage(ContentPage page)
            {
                // I check current month from page title, it's a shame
                if (!DateTime.TryParse(page?.Title,
                                       DateTimeFormatInfo.InvariantInfo,
                                       DateTimeStyles.None,
                                       out DateTime newDate))
                    return;
    
                // My extension method, but you can use DateTime.Equals(DateTime) method
                if (newDate.DateEqualsByDay(CurrentDate))
                    return;
    
                // Calculate current position and Time
                _previousDate = CurrentDate;
                CurrentDate = newDate;
    
                PageIndex += newDate < _previousDate ? -1 : 1;
    
    
                // Load Current Data
                var month = CurrentMonth;
    
                if (month.Weeks is null)
                {
                    await Task.Run(async () => { month.Weeks = await GetWeeks(month); });
                }
    
                AddNewMonth(month);
            }
    
            #endregion
    
    
            #region Override Methods
    
            public override async Task OnPageAppearing()
            {
                if (Months != null)
                    return;
    
                await Task.Run(() =>
                {
                    Months = MonthFactory.GetMonths(DateTime.Now, ItemTappedCommand);
                    CurrentDate = Months[0].FirstDayOfMonth;
                });
            }
    
            #endregion
    
    
            #region Private Methods 
    
    
            private void AddNewMonth(MonthInfo month)
            {
                // Add to the head
                if (month.Equals(Months[0]))
                {
                    Months.Insert(0, MonthFactory.GetLeft(month.FirstDayOfMonth, ItemTappedCommand));
                    PageIndex++;
                }
                // Add to the tail      
                else if (month.Equals(Months[_pagesCount - 1]))
                    Months.Add(MonthFactory.GetRight(month.FirstDayOfMonth, ItemTappedCommand));
                else
                    return;
    
                _pagesCount++;
            }
    
            //...............
    
  • TobyKTobyK GBMember ✭✭✭

    I'm currently working on a carousel picker - starting with some options in a listview on a single carousel page and
    then based on the selected item, pages are created and opened on the fly (Another CarouselView Page with another listview). Took me a while to find the property - but I found it in the end and I bind my SelectedIndex to the carousel's Position property and make it Mode=TwoWay so that I can programmatically page to the newly created page that way.

  • FotalFotal Member ✭✭✭
    The main reason of using CarouselPage Instead of ListView, this is the performance issues. If you have a complex layout you should use a CarouselPage.
    I also tried to use CarouselView plugin, but it has the same performance problem.
Sign In or Register to comment.