Pages

Monday, 14 January 2019

Create your own Calendar control in Xamarin Forms


The Xamarin. Forms Calendar control allows users to easily select single dates like built-in calendar. It provides smooth gestures for navigation between month and year views. The key features include the following:
  • Ability to disable date selection for certain dates and within a specific date range.
  • Appearance customization.
  • Fully Customization as it is not used any DLL, you can enhance this code according to your choice and need.
  • Some of the Design is created at runtime in C#. Therefore, it is more flexible and extendable.




So Let’s Start with Design File:
TestControl.xaml

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="Test.TestControl"
xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:custom="clr-namespace:CustomControl.CalendarControl”>
<StackLayout>
<Grid RowSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="50" />
<RowDefinition Height="1" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="50" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5" />
<ColumnDefinition Width="1" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="6*" />
</Grid.ColumnDefinitions>


<custom:CalendarControl
x:Name="ccControls"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="4"
BackgroundColor="SkyBlue"
DateClicked="ccControls_DateClicked" />


<BoxView
x:Name="line"
Grid.Row="1"
Grid.RowSpan="24"
Grid.Column="1"
BackgroundColor="Black"
HeightRequest="1"
HorizontalOptions="FillAndExpand"
WidthRequest="1" />

<Label
x:Name="lbltime"
Grid.Row="1"
Grid.Column="2"
Margin="-3,0,-3,0"
FontSize="8"
Text="---12PM" />
<Label
x:Name="lblEvent"
Grid.Row="1"
Grid.Column="3"
FontSize="10"
LineBreakMode="TailTruncation"
Text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. " />
<BoxView
x:Name="horizontalline"
Grid.Row="2"
Grid.Column="1"
Grid.ColumnSpan="3"
BackgroundColor="Black"
HeightRequest="1"
HorizontalOptions="FillAndExpand"
WidthRequest="1" />



</Grid>
</StackLayout>
</ContentPage>


 

Please note below Code will take three parameter and one Event Handler : Month, Year, SelectedDay and Date Clicked Event for getting current date. For details understanding we will check the controls below and understand how this is working.

--------------------------------------------------------------------------------------

<custom:CalendarControl
x:Name="ccControls"
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="4"
BackgroundColor="SkyBlue"
DateClicked="ccControls_DateClicked" />

--------------------------------------------------------------------------------------



Code Behind File :


(TestControl.xaml.cs)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Test
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class TestControl : ContentPage
{
public TestControl()
{
InitializeComponent();
ccControls.Month = Convert.ToString(DateTime.Now.Month);
ccControls.Year = Convert.ToString(DateTime.Now.Year);
ccControls.SelectedDay = Convert.ToString(DateTime.Now.Day);
}

private async void ccControls_DateClicked(object sender, EventArgs e)
{
var item = (Xamarin.Forms.Button)sender;
await DisplayAlert("Demo Project", "Date Clicked " + item.Text, "Ok");
}
}
}



Now check below design for calander controls


CalendarControl.xaml   (Design File)

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="CustomControls.CalendarControl">
<ContentView.Content>
<AbsoluteLayout>
<ScrollView Padding="0,0,0,0" AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutFlags="All" BackgroundColor="White" VerticalOptions="Fill">
<StackLayout Margin="5,5,5,5">
<StackLayout>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="5" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="5" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Image x:Name="btnBack" Grid.Row="1" Grid.Column="1" Aspect="AspectFit" HeightRequest="15" IsVisible="True" Source="arrow_left" VerticalOptions="Center" WidthRequest="15">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="btnBack_Clicked" />
</Image.GestureRecognizers>
</Image>
<Label x:Name="lblSelectedMonth" Grid.Row="1" Grid.Column="2" FontSize="14" HorizontalOptions="Center" IsVisible="True" Text="January" TextColor="Black" VerticalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="lblmonth_tapped" />
</Label.GestureRecognizers>
</Label>
<Label x:Name="lblDash" Grid.Row="1" Grid.Column="3" FontSize="14" HorizontalOptions="Center" IsVisible="True" Text="-" TextColor="Black" VerticalOptions="Center" />
<Entry x:Name="lblM" IsVisible="False" />
<Entry x:Name="lblY" IsVisible="False" />
<Label x:Name="lblMonth" IsVisible="False" />
<Entry x:Name="lblSelectedDay" IsVisible="False" />
<Label x:Name="lblYear" Grid.Row="1" Grid.Column="4" FontSize="14" HorizontalOptions="Center" IsVisible="True" Text="2018" TextColor="Black" VerticalOptions="Center">
<Label.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="lblYear_tapped" />
</Label.GestureRecognizers>
</Label>
<Image x:Name="btnForward" Grid.Row="1" Grid.Column="5" Aspect="AspectFit" HeightRequest="15" HorizontalOptions="Center" IsVisible="True" Source="arrow_right" VerticalOptions="Center" WidthRequest="15">
<Image.GestureRecognizers>
<TapGestureRecognizer NumberOfTapsRequired="1" Tapped="btnForward_Clicked" />
</Image.GestureRecognizers>
</Image>
</Grid>
</StackLayout>
<ContentView x:Name="gridstack" />
</StackLayout>
</ScrollView>
<ContentView x:Name="overlay" Padding="10,0" AbsoluteLayout.LayoutBounds="0, 0, 1, 1" AbsoluteLayout.LayoutFlags="All" BackgroundColor="#C0808080" IsVisible="False">
<ScrollView Padding="0,0,2,0" VerticalOptions="Fill">
<StackLayout Margin="0,20,0,0" BackgroundColor="Black" HeightRequest="400" HorizontalOptions="Center" Orientation="Vertical" VerticalOptions="Start" WidthRequest="200">
<Label x:Name="lblDialogTitle" BackgroundColor="Black" FontSize="14" HorizontalOptions="CenterAndExpand" Text="Select" TextColor="White" VerticalOptions="CenterAndExpand" />
<ListView x:Name="lstView_data" BackgroundColor="White" HasUnevenRows="False" HorizontalOptions="FillAndExpand" ItemSelected="lstView_data_ItemSelected" SeparatorVisibility="None">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<StackLayout Padding="0" BackgroundColor="White" Spacing="0">
<Grid Padding="5" BackgroundColor="White" ColumnSpacing="0">
<Grid.RowDefinitions>
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Font="Bold" FontSize="12" IsVisible="False" Text="{Binding dataid}" TextColor="Black" />
<Label Grid.Row="0" Grid.Column="1" Font="Normal" FontSize="12" HorizontalOptions="CenterAndExpand" Text="{Binding data}" TextColor="Black" VerticalOptions="CenterAndExpand" />
</Grid>
<BoxView BackgroundColor="#f1f1f1" HeightRequest="1" />
</StackLayout>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
<StackLayout>
<Button x:Name="dialogclose" BackgroundColor="Black" Clicked="dialogclose_Clicked" FontSize="14" HeightRequest="40" Text="Close" TextColor="White" />
</StackLayout>
</StackLayout>
</ScrollView>
</ContentView>
</AbsoluteLayout>
</ContentView.Content>
</ContentView>

Code Behind File:


Note: Following Code is also contains some dynamic control which will generate at runtime and bind it with Xamal Code ( x:Name="overlay" )


using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace CustomControls
{
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CalendarControl : ContentView
{
public event EventHandler DateClicked;
public static readonly BindableProperty YearProperty = BindableProperty.Create(
propertyName: "Year",
returnType: typeof(string),
declaringType: typeof(CalendarControl),
defaultValue: "2018",
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: YearPropertyChanged);

public static readonly BindableProperty MonthProperty = BindableProperty.Create(
propertyName: "Month",
returnType: typeof(string),
declaringType: typeof(CalendarControl),
defaultValue: "1",
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: MonthPropertyChanged);

public static readonly BindableProperty SelectedDayProperty = BindableProperty.Create(
propertyName: "SelectedDay",
returnType: typeof(string),
declaringType: typeof(CalendarControl),
defaultValue: "0",
defaultBindingMode: BindingMode.TwoWay,
propertyChanged: SelectedDayPropertyChanged);
public string SelectedDay
{
get { return GetValue(SelectedDayProperty).ToString(); }
set { SetValue(SelectedDayProperty, value); }
}
public  string Year
{
get { return GetValue(YearProperty).ToString(); }
set { SetValue(YearProperty, value); }
}
private static void YearPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CalendarControl)bindable;
control.lblYear.Text = newValue.ToString();
control.lblY.Text = control.lblYear.Text;
}
private static void SelectedDayPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CalendarControl)bindable;
control.lblSelectedDay.Text = newValue.ToString();
}
public  string Month
{
get { return GetValue(MonthProperty).ToString(); }
set { SetValue(MonthProperty, value); }
}
private static void MonthPropertyChanged(BindableObject bindable, object oldValue, object newValue)
{
var control = (CalendarControl)bindable;
control.lblMonth.Text = newValue.ToString();
control.lblM.Text = control.lblMonth.Text;
}
long SelectedYear;
string SelectedMonth = "";
static int   SelectedMonthInt = 0;
string selectedday;
Grid gridCalander;
public CalendarControl()
{
InitializeComponent();
lblM.TextChanged += MonthChanged_TextChanged;
lblY.TextChanged += YearChanged_TextChanged;
lblM.TextChanged += MonthChanged_TextChanged;
lblSelectedDay.TextChanged+= YearChanged_TextChanged;
}
private void YearChanged_TextChanged(object sender, TextChangedEventArgs e)
{
var y = Year;
var m = Month;
selectedday = SelectedDay;
SelectedYear = Convert.ToInt64(y);
lblSelectedMonth.Text = new DateTime(Convert.ToInt32(y), Convert.ToInt32(m), 1).ToString("MMMM");
SelectedMonthInt = Convert.ToInt32(m);
lblYear.Text = Convert.ToString(y);
CreateGridCalander();
CreateDate(selectedday);
}
private void MonthChanged_TextChanged(object sender, TextChangedEventArgs e)
{
var y = Year;
var m = Month;
selectedday = SelectedDay;
SelectedYear = Convert.ToInt64(y);
lblSelectedMonth.Text = new DateTime(Convert.ToInt32(y), Convert.ToInt32(m), 1).ToString("MMMM");
SelectedMonthInt = Convert.ToInt32(m);
lblYear.Text = Convert.ToString(y);
CreateGridCalander();
CreateDate(selectedday);
}
private void CreateGridCalander()
{
#region Grid Calander
gridCalander = new Grid
{
BackgroundColor = Color.LightBlue,
Padding = 0,
ColumnSpacing = 0,
Margin = new Thickness(10, 0, 10, 0),
RowDefinitions =
{
new RowDefinition { Height = 1 },
new RowDefinition { Height = 50 },
new RowDefinition { Height = 1 },
new RowDefinition { Height = 50 },
new RowDefinition { Height = 1 },
new RowDefinition { Height = 50 },
new RowDefinition { Height = 1 },
new RowDefinition { Height = 50 },
new RowDefinition { Height = 1 },
new RowDefinition { Height = 50 },
new RowDefinition { Height = 1 },
new RowDefinition { Height = 50 },
new RowDefinition { Height = 1 },
new RowDefinition { Height = 50 },
new RowDefinition { Height = 1 },


},
ColumnDefinitions =
{
new ColumnDefinition { Width =  1 },
new ColumnDefinition { Width =  50 },
new ColumnDefinition { Width =  1 },
new ColumnDefinition { Width =  50 },
new ColumnDefinition { Width =  1 },
new ColumnDefinition { Width =  50 },
new ColumnDefinition { Width =  1 },
new ColumnDefinition { Width =  50 },
new ColumnDefinition { Width =  1 },
new ColumnDefinition { Width =  50 },
new ColumnDefinition { Width =  1 },
new ColumnDefinition { Width =  50 },
new ColumnDefinition { Width =  1 },
new ColumnDefinition { Width =  50 },
new ColumnDefinition { Width =  1 },

}
};
Button[] btn = new Button[7];
string[] weekname = new string[7] { "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su" };
for (int i = 0; i <= 6; i++)
{
btn[i] = new Button()
{
Text = weekname[i],
BackgroundColor = Color.Transparent,
FontSize = 10,
CornerRadius = 0,
FontAttributes = FontAttributes.Bold,
TextColor = Color.Black,
HorizontalOptions = LayoutOptions.Center,
VerticalOptions = LayoutOptions.Center
};
}
int k = 1, l = 1;
for (int i = 0; i <= 6; i++)
{
gridCalander.Children.Add(btn[i], k, l);
k = k + 2;
}
// Grid.SetColumnSpan(Title, 5);
BoxView[] boxr = new BoxView[8];

for (int i = 0; i <= 7; i++)
{
boxr[i] = new BoxView()
{
HeightRequest = 1,
BackgroundColor = Color.Black,
HorizontalOptions = LayoutOptions.FillAndExpand,

};

}
k = 0; l = 0;
// Creating Vertical Line
for (int i = 0; i <= 7; i++)
{
gridCalander.Children.Add(boxr[i], k, l);
Grid.SetRowSpan(boxr[i], 15);
k = k + 2;
}

k = 0; l = 0;

BoxView[] boxc = new BoxView[8];

for (int i = 0; i <= 7; i++)
{
boxc[i] = new BoxView()
{
HeightRequest = 1,
BackgroundColor = Color.Black,
HorizontalOptions = LayoutOptions.FillAndExpand,
};
}
// Creating Horizontal line
for (int i = 0; i <= 7; i++)
{
gridCalander.Children.Add(boxc[i], k, l);
Grid.SetColumnSpan(boxc[i], 15);
l = l + 2;
}
gridstack.Content = null;
gridstack.Content = gridCalander;

#endregion
}

private void btnBack_Clicked(object sender, EventArgs e)
{
var x = SelectedMonthInt;
}

private void btnForward_Clicked(object sender, EventArgs e)
{

}


private void lblmonth_tapped(object sender, EventArgs e)
{
lblDialogTitle.Text = "Select Month";
lstView_data.ItemsSource = null;
try
{
System.Collections.Generic.List<DataMaster> VList = new List<DataMaster>();
for (int i = 1; i <= 12; i++)
{
VList.Add(new DataMaster() { dataid = i, data = CultureInfo.CurrentUICulture.DateTimeFormat.MonthNames[i - 1] });
}
lstView_data.ItemsSource = VList;
}
catch
{

}

overlay.IsVisible = true;
}


private void lblYear_tapped(object sender, EventArgs e)
{
lblDialogTitle.Text = "Select Year";
lstView_data.ItemsSource = null;
try
{
List<int> listYears = Enumerable.Range(1930, DateTime.Now.AddYears(50).Year - 1930 + 1).ToList();
List<DataMaster> VList = new List<DataMaster>();
for (int i = 1; i < listYears.Count; i++)
{
VList.Add(new DataMaster() { dataid = i, data = Convert.ToString(listYears[i]) });
}

lstView_data.ItemsSource = VList;
}
catch
{

}

overlay.IsVisible = true;
}

private void dialogclose_Clicked(object sender, EventArgs e)
{
overlay.IsVisible = false;
}

private void lstView_data_ItemSelected(object sender, SelectedItemChangedEventArgs e)
{
#region FirstdayLastDay

var item1 = (DataMaster)e.SelectedItem;
var isNumeric = int.TryParse(item1.data, out int n);



if (isNumeric == true)
{
SelectedYear = Convert.ToInt64(item1.data);
lblYear.Text = Convert.ToString(SelectedYear);
}
else
{
SelectedMonth = Convert.ToString(item1.data);
SelectedMonthInt = Convert.ToInt32(item1.dataid);
lblSelectedMonth.Text = SelectedMonth;
}
if (SelectedMonthInt == DateTime.Now.Month && SelectedYear == DateTime.Now.Year)
{
CreateDate(Convert.ToString(DateTime.Now.Day));
}
else
{
CreateDate("0");
}

//----------------------------------------------------------------------
#endregion
}

private void CreateDate(string selectedday)
{
Button[] btn = new Button[31];

//getting First Day and Last Day---------------------------------------

DateTime startOfMonth = new DateTime(Convert.ToInt32(SelectedYear), SelectedMonthInt, 1);   //new DateTime(year, month, 1);

DateTime endOfMonth = new DateTime(Convert.ToInt32(SelectedYear), SelectedMonthInt, DateTime.DaysInMonth(Convert.ToInt32(SelectedYear), SelectedMonthInt)); //new DateTime(year, month, DateTime.DaysInMonth(year, month));

// DateTime dtSelectedDate = DateTime.Now;
string dtFirstDayOfMonth = startOfMonth.ToString("ddd");
string dtLastDayOfMonth = endOfMonth.ToString("ddd");

int day = (int)startOfMonth.DayOfWeek;


gridCalander.Children.Clear();
CreateGridCalander();
for (int i = startOfMonth.Day; i <= endOfMonth.Day; i++)
{
FontAttributes fontattributes = FontAttributes.None;
Color color = Color.Transparent;
Color colortext = Color.Black;
if (i==Convert.ToInt32(selectedday))
{
fontattributes = FontAttributes.Bold;
color = Color.White;
colortext = Color.Red;
}


btn[i - 1] = new Button()
{
Text = i.ToString(),
FontSize = 12,
FontAttributes= fontattributes,
BackgroundColor = color,
TextColor= colortext


};
btn[i - 1].Clicked += Calendar_Clicked;
}
int r = 3;
int c = 0;
if (day == 1)
{
c = 1;
}
else if (day == 2)
{
c = 3;
}
else if (day == 3)
{
c = 5;
}
else if (day == 4)
{
c = 7;
}
else if (day == 5)
{
c = 9;
}
else if (day == 6)
{
c = 11;
}
else
{
c = 13;
}


int m = 1;

// adding button to gridview

for (int k = 1; k <= 7; k++)
{
for (int j = 1; j <= 7; j = j + 1)
{
if (m > endOfMonth.Day)
{
break;
}
else
{

gridCalander.Children.Add(btn[m - 1], c, r);
c = c + 2;
m++;

if (c == 15)
{
break;
}
}
}
c = 1;
r = r + 2;
}
overlay.IsVisible = false;
}
private void Calendar_Clicked(object sender, EventArgs e)
{
DateClicked?.Invoke(sender, e);
//await ("Demo Project", "Date Clicked " + item.Text, "Ok");
}
}
}


Please Follow for more post and leave comment.





6 comments:

  1. Hey,

    This is really very helpful information about calendar control.
    Thanks for sharing!!

    Hire Xamarin Developer Texas

    ReplyDelete
  2. This code doesn't compile because the DataMaster class is missing.

    ReplyDelete
    Replies
    1. here the code: public class DataMaster
      {
      public int dataid { get; set; }
      public string data { get; set; }
      }

      Delete
  3. Yo Ralph Thanks for taking your time out to do this, this has helped me with 2 of my projects, i appreciate it

    ReplyDelete
  4. How to update grid when month btnForward is clicked?

    ReplyDelete
  5. Nice blog! I really loved reading through this Blog... Thanks for sharing such a very interesting post with us and keep blogging.
    Visit our website-
    hire xamarin developer
    web development company
    android development company

    ReplyDelete