Disabling (Black Out) Dates in DatePicker in Silverlight With MVVM


Introduction

Let us take a ticket booking example which is best suited for disabling dates. When I try to book a ticket, I should not be able to book a journey ticket for past dates and the return ticket date should not precede the starting date for the journey ticket.

disableimage.gif

Styles for Calendar

Step 1 : If you use Expression Blend we can easily do the custom styles. The following code is generated from Expression blend and I made a few changes for my project requirements such as color and design.

  • Add New Item->Silverlight Resource Dictionary.

s2.gif

Step 2 : Add the following code in the resource dictionary (styles.xaml).

Code

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows"
    xmlns:primitives="clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls"     
    xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls">
    <Style x:Key="myCalendarDayButtonStyle" TargetType="primitives:CalendarDayButton">
        <Setter Property="Background" Value="Aqua"/>
        <Setter Property="FontSize" Value="10"/>
        <Setter Property="HorizontalContentAlignment" Value="Center"/>
        <Setter Property="VerticalContentAlignment" Value="Center"/>
        <Setter Property="MinWidth" Value="4"/>
        <Setter Property="MinHeight" Value="4"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="primitives:CalendarDayButton">
                    <Grid Name="path">
                        <vsm:VisualStateManager.VisualStateGroups>
                            <vsm:VisualStateGroup x:Name="CommonStates">
                                <vsm:VisualStateGroup.Transitions>
                                    <vsm:VisualTransition GeneratedDuration="0:0:0.1"/>
                                </vsm:VisualStateGroup.Transitions>
                                <vsm:VisualState x:Name="Normal"/>
                                <vsm:VisualState x:Name="MouseOver">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="Opacity" To=".5"/>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="Pressed">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="Opacity" To=".5"/>
                                    </Storyboard>
                                </vsm:VisualState>|
                                <vsm:VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="Background" Storyboard.TargetProperty="Opacity" To="0"/>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="Content" Storyboard.TargetProperty="Opacity" To=".35"/>
                                    </Storyboard>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
                            <vsm:VisualStateGroup x:Name="SelectionStates">|
                                <vsm:VisualStateGroup.Transitions>
                                    <vsm:VisualTransition GeneratedDuration="0"/>
                                </vsm:VisualStateGroup.Transitions>
                                <vsm:VisualState x:Name="Unselected"/>
                                <vsm:VisualState x:Name="Selected">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="SelectedBackground" Storyboard.TargetProperty="Opacity" To=".75"/>
                                    </Storyboard>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
                            <vsm:VisualStateGroup x:Name="CalendarButtonFocusStates">
                                <vsm:VisualStateGroup.Transitions>
                                    <vsm:VisualTransition GeneratedDuration="0"/>
                                </vsm:VisualStateGroup.Transitions>
                                <vsm:VisualState x:Name="CalendarButtonFocused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="FocusVisual" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="CalendarButtonUnfocused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="FocusVisual" Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
                            <vsm:VisualStateGroup x:Name="ActiveStates">
                                <vsm:VisualStateGroup.Transitions>
                                    <vsm:VisualTransition GeneratedDuration="0"/>
                                </vsm:VisualStateGroup.Transitions>
                                <vsm:VisualState x:Name="Active"/>
                                <vsm:VisualState x:Name="Inactive">
                                    <Storyboard>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="ContentBrush" Storyboard.TargetProperty="Color" To="#FF777777"/>
                                    </Storyboard>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
                            <vsm:VisualStateGroup x:Name="DayStates">
                                <vsm:VisualStateGroup.Transitions>
                                    <vsm:VisualTransition GeneratedDuration="0"/>
                                </vsm:VisualStateGroup.Transitions>
                                <vsm:VisualState x:Name="RegularDay"/>
                                <vsm:VisualState x:Name="Today">
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
                            <vsm:VisualStateGroup x:Name="BlackoutDayStates" >
                                <vsm:VisualStateGroup.Transitions>|
                                    <vsm:VisualTransition GeneratedDuration="0"/>
                                </vsm:VisualStateGroup.Transitions>
                                <vsm:VisualState x:Name="NormalDay">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="BlackoutVisual" Storyboard.TargetProperty="Opacity" To="0"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="ContentBrush" Storyboard.TargetProperty="Color" To="Black"/>
                                    </Storyboard>
                                </vsm:VisualState>
                                <vsm:VisualState x:Name="BlackoutDay">
                                    <Storyboard>
                                        <DoubleAnimation Duration="0" Storyboard.TargetName="BlackoutVisual" Storyboard.TargetProperty="Opacity" To=".1"/>
                                        <ColorAnimation Duration="0" Storyboard.TargetName="ContentBrush" Storyboard.TargetProperty="Color" To="#ACACAC"/>
                                    </Storyboard>
                                </vsm:VisualState>
                            </vsm:VisualStateGroup>
                        </vsm:VisualStateManager.VisualStateGroups>
                        <Rectangle x:Name="TodayBackground" Fill="#6DBDD1" RadiusX="1" RadiusY="1" Opacity="0"/>
                        <Rectangle x:Name="SelectedBackground" Fill="{TemplateBinding Background}" RadiusX="1" RadiusY="1" Opacity="0"/>
                        <Rectangle x:Name="Background" Fill="{TemplateBinding Background}" RadiusX="1" RadiusY="1" Opacity="0"/>
                        <ContentControl x:Name="Content" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="5,1,5,1" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" FontSize="{TemplateBinding FontSize}" IsTabStop="False" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}">
                            <ContentControl.Foreground>
                                <SolidColorBrush x:Name="ContentBrush" Color="Black"/>
                            </ContentControl.Foreground>
                        </ContentControl>
                        <Rectangle x:Name="BlackoutVisual"  Stroke="Transparent" StrokeThickness="0" Fill="#F0F0F1" />
                        <Rectangle x:Name="FocusVisual" Stroke="#FF6DBDD1" RadiusX="1" RadiusY="1" IsHitTestVisible="false" Visibility="Collapsed"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="myCalendarStyle" TargetType="controls:Calendar">
        <Setter Property="IsTabStop" Value="False"/>
        <Setter Property="Background" >
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFD3DEE8" Offset="0"/>
                    <GradientStop Color="#FFD3DEE8" Offset="0.16"/>
                    <GradientStop Color="#FFFCFCFD" Offset="0.16"/>
                    <GradientStop Color="#FFFFFFFF" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="BorderBrush"  >
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#FFA3AEB9" Offset="0"/>
                    <GradientStop Color="#FF8399A9" Offset="0.375"/>
                    <GradientStop Color="#FF718597" Offset="0.375"/>
                    <GradientStop Color="#FF617584" Offset="1"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="controls:Calendar">
                    <StackPanel x:Name="Root" HorizontalAlignment="Center">
                        <primitives:CalendarItem x:Name="CalendarItem" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
        <Setter Property="CalendarDayButtonStyle" Value="{StaticResource myCalendarDayButtonStyle}"/>
    </Style>
</
ResourceDictionary>

Step 3 :
Now add this resource dictionary to app.xaml.

Code
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="BlackoutDatesSL.App"
             xmlns:style="clr-namespace:BlackoutDatesSL">
    <Application.Resources>
        <ResourceDictionary Source="Styles.xaml"/>
    </Application.Resources>
</
Application>

Step 4 :  Now add the calendar style to datepicker in MainPage.xaml.

Code
<UserControl x:Class="BlackoutDatesSL.MainPage"
    xmlns:sdk="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:viewmodel="clr-namespace:BlackoutDatesSL"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">
    <UserControl.Resources>
        <viewmodel:ViewModel x:Key="ViewModel"/>
    </UserControl.Resources>
    <Grid DataContext="{Binding Source={StaticResource ViewModel}}" Name="LayoutRoot">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TextBlock Text="From Date:" Grid.Row="0" Grid.Column="0" VerticalAlignment="Center"/>
        <TextBlock Text="To Date:" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"/>
        <sdk:DatePicker TabIndex="1"  Grid.Row="0" Grid.Column="1" Width="100" Name="FromDate"
                        SelectedDateChanged="FromDate_SelectedDateChanged" CalendarStyle="{StaticResource myCalendarStyle}"
                        SelectedDate="{Binding FromDate,Mode=TwoWay}"/>
        <sdk:DatePicker TabIndex="2" Grid.Row="1"  Width="100" Grid.Column="1" Name="ToDate"
                        CalendarStyle="{StaticResource myCalendarStyle}"  SelectedDate="{Binding ToDate,Mode=TwoWay}" />
    </Grid>
</
UserControl>\

Step 5 :  Now we have to add the logic for blackout dates.

  • When the page is loading, we should not display past dates in the date picker and after selecting a date, we should disable dates in the ToDate datepicker that precede starting date. The following is the code which will disable dates until yesterday.

Code

FromDate.BlackoutDates.Add(new CalendarDateRange(new DateTime(), DateTime.Now.AddDays(-1)));
                 ToDate.BlackoutDates.Add(new CalendarDateRange(new DateTime(), DateTime.Now.AddDays(-1)));

And in the selected date changed event of FromDate:

private void FromDate_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
        {
            if (((ViewModel)DataContext).ToDate != null)
            {
                if (((ViewModel)DataContext).ToDate < FromDate.SelectedDate)
                    ((ViewModel)DataContext).ToDate = null;
            }
            this.ToDate.BlackoutDates.Clear();
            this.ToDate.BlackoutDates.Add(new CalendarDateRange(new DateTime(), ((DateTime)this.FromDate.SelectedDate).AddDays(-1)));
        }

My complete xaml.cs looks like below.

using
System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace BlackoutDatesSL
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            FromDate.BlackoutDates.Add(new CalendarDateRange(new DateTime(), DateTime.Now.AddDays(-1)));
            ToDate.BlackoutDates.Add(new CalendarDateRange(new DateTime(), DateTime.Now.AddDays(-1)));
            this.DataContext = LayoutRoot.DataContext;
        }
        private void FromDate_SelectedDateChanged(object sender, SelectionChangedEventArgs e)
        {
            if (((ViewModel)DataContext).ToDate != null)
            {
                if (((ViewModel)DataContext).ToDate < FromDate.SelectedDate)
                    ((ViewModel)DataContext).ToDate = null;
            }
            this.ToDate.BlackoutDates.Clear();
            this.ToDate.BlackoutDates.Add(new CalendarDateRange(new DateTime(), ((DateTime)this.FromDate.SelectedDate).AddDays(-1)));
        }
    }
}

My ViewmodelBase and ViewModel:

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace BlackoutDatesSL
{
    public class ViewModelBase : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

ViewModel.cs

using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace BlackoutDatesSL
{
    public class ViewModel : ViewModelBase
    {
        private DateTime? _fromDate;
        public DateTime? FromDate
        {
            get
            {
                return _fromDate;
            }
            set
            {
                _fromDate = value;
                NotifyPropertyChanged("FromDate");
            }
        }
        private DateTime? _toDate;
        public DateTime? ToDate
        {
            get
            {
                return _toDate;
            }
            set
            {
                _toDate = value;
                NotifyPropertyChanged("ToDate");
            }
        }
    }
}

Step 6 : Now run the application and play with the date controls. I have attached complete source code.

If you find the above article useful, please rate and share the documents with your friends.


Similar Articles