Click here to Skip to main content
15,881,172 members
Articles / Web Development / ASP.NET

Signum Framework Principles

Rate me:
Please Sign up or sign in to vote.
4.74/5 (27 votes)
25 Jul 2011CPOL18 min read 98.7K   1.1K   86  
Explains the philosophy behind Signum Framework, an ORM with a full LINQ Provider that encourages an entities-first approach.
//---------------------------------------------------------------------------
//
// Copyright (C) Microsoft Corporation.  All rights reserved.
//
//---------------------------------------------------------------------------

using System;
using System.Collections;              // IEnumerable
using System.Collections.Generic;      // List<T>
using System.Collections.ObjectModel;  // ReadOnlyCollection<T>
using System.Collections.Specialized;  // NotifyCollectionChangedEventArgs
using System.Diagnostics;       // Debug
using System.Globalization;     // CultureInfo
using System.Windows;
using System.Windows.Controls;  // Control
using System.Windows.Controls.Primitives;
using System.Windows.Data;      // IValueConverter
using System.Windows.Input;     // RoutedCommand
using System.Windows.Media;     
using System.Windows.Threading; // DispatcherPriority
using System.Xml;               // XmlAttribute
using System.Linq;
using Signum.Utilities;
using Signum.Windows.DateUtils;


namespace Signum.Windows
{
    /// <summary>
    /// The month calendar control implements a calendar-like user interface,
    /// that provides the user with a very intuitive and recognizable method
    /// of selecting a date, a contiguous or discrete ranges of dates using
    /// a visual display. Users can customize the look of the calendar portion
    /// of the control by setting titles, dates, fonts and backgrounds.
    /// </summary>
    [TemplatePart(Name = "PART_VisibleDaysHost", Type = typeof(MonthCalendarContainer))]
    [TemplatePart(Name = "PART_PreviousButton", Type = typeof(ButtonBase))]
    [TemplatePart(Name = "PART_NextButton", Type = typeof(ButtonBase))]
    public class MonthCalendar : Control
    {
        //-------------------------------------------------------------------
        //
        //  Constructors
        //
        //-------------------------------------------------------------------

        #region Constructors

        /// <summary>
        /// Static Constructor
        /// </summary>
        static MonthCalendar()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(MonthCalendar), new FrameworkPropertyMetadata(typeof(MonthCalendar)));

            _gotoCommand = new RoutedCommand("Goto", typeof(MonthCalendar));
            CommandManager.RegisterClassCommandBinding(typeof(MonthCalendar), new CommandBinding(MonthCalendar.GotoCommand, new ExecutedRoutedEventHandler(OnExecuteGotoCommand), new CanExecuteRoutedEventHandler(OnQueryGotoCommand)));

            _nextCommand = new RoutedCommand("Next", typeof(MonthCalendar));
            CommandManager.RegisterClassCommandBinding(typeof(MonthCalendar), new CommandBinding(MonthCalendar.NextCommand, new ExecutedRoutedEventHandler(OnExecuteNextCommand), new CanExecuteRoutedEventHandler(OnQueryNextCommand)));
            CommandManager.RegisterClassInputBinding(typeof(MonthCalendar), new InputBinding(MonthCalendar.NextCommand, new KeyGesture(Key.PageDown)));

            _previousCommand = new RoutedCommand("Previous", typeof(MonthCalendar));
            CommandManager.RegisterClassCommandBinding(typeof(MonthCalendar), new CommandBinding(MonthCalendar.PreviousCommand, new ExecutedRoutedEventHandler(OnExecutePreviousCommand), new CanExecuteRoutedEventHandler(OnQueryPreviousCommand)));
            CommandManager.RegisterClassInputBinding(typeof(MonthCalendar), new InputBinding(MonthCalendar.PreviousCommand, new KeyGesture(Key.PageUp)));

            IsTabStopProperty.OverrideMetadata(typeof(MonthCalendar), new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));
            KeyboardNavigation.DirectionalNavigationProperty.OverrideMetadata(typeof(MonthCalendar), new FrameworkPropertyMetadata(KeyboardNavigationMode.Contained));
            KeyboardNavigation.TabNavigationProperty.OverrideMetadata(typeof(MonthCalendar), new FrameworkPropertyMetadata(KeyboardNavigationMode.Once));
        }

        /// <summary>
        /// Default Constructor
        /// </summary>
        public MonthCalendar()
            : base()
        {}

        #endregion

        //-------------------------------------------------------------------
        //
        //  Public Methods
        //
        //-------------------------------------------------------------------

        #region Public Methods

        /// <summary>
        /// Returns a string representation for this control.
        /// </summary>
        public override string ToString()
        {
            return base.ToString() + " VisibleMonth: " + VisibleMonth.ToShortDateString() + ", SelectedDate: " + SelectedDate.ToString();
        }

        /// <summary>
        /// Called when the Template's tree has been generated
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            if (_mccContainer != null)
            {
                _mccContainer.SelectionChanged -= new SelectionChangedEventHandler(OnContainerSelectionChanged);
                _mccContainer = null;
            }

            // Walk the visual tree to find the MonthCalendarContainer.
            _mccContainer = GetTemplateChild(c_VisibleDaysHostTemplateName) as MonthCalendarContainer;

            if (_mccContainer != null)
            {
                _mccContainer.ItemsSource = VisibleDays;

                RefreshDayTemplate();

                _mccContainer.SelectionChanged += new SelectionChangedEventHandler(OnContainerSelectionChanged);
            }

            RefreshPreviousButtonStyle();
            RefreshNextButtonStyle();
        }

        /// <summary>
        /// Return the UI element corresponding to the given date.
        /// Returns null if the date does not belong to the visible days
        /// or if no UI has been generated for it.
        /// </summary>
        public MonthCalendarItem GetContainerFromDate(DateTime date)
        {
            CalendarDate cdate = GetCalendarDateByDate(date);
            if (cdate != null && _mccContainer != null)
            {
                return _mccContainer.ItemContainerGenerator.ContainerFromItem(cdate) as MonthCalendarItem;
            }
            return null;
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Public Events
        //
        //-------------------------------------------------------------------

        #region Public Events


        public static readonly RoutedEvent SelectedDateChangedEvent = EventManager.RegisterRoutedEvent(
            "SelectedDateChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<DateTime?>), typeof(MonthCalendar));
        public event RoutedPropertyChangedEventHandler<DateTime?> SelectedDateChanged
        {
            add { AddHandler(SelectedDateChangedEvent, value); }
            remove { RemoveHandler(SelectedDateChangedEvent, value); }
        }


        /// <summary>
        /// An event fired when the display month switches
        /// </summary>
        public static readonly RoutedEvent VisibleMonthChangedEvent = EventManager.RegisterRoutedEvent(
            "VisibleMonthChanged", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<DateTime>), typeof(MonthCalendar));

        /// <summary>
        /// Add / Remove VisibleMonthChangedEvent handler
        /// </summary>
        public event RoutedPropertyChangedEventHandler<DateTime> VisibleMonthChanged
        {
            add { AddHandler(VisibleMonthChangedEvent, value); }
            remove { RemoveHandler(VisibleMonthChangedEvent, value); }
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Public Commands
        //
        //-------------------------------------------------------------------

        #region Public Commands

        private static RoutedCommand _gotoCommand = null;

        /// <summary>
        /// Go to month
        /// </summary>
        /// <remarks>
        /// if the argument is null,     GotoCommand will switch to DateTime.Now
        /// if the argument is DateTime, GotoCommand will switch to the month from the argument
        /// </remarks>
        public static RoutedCommand GotoCommand
        {
            get { return _gotoCommand; }
        }

        private static void OnQueryGotoCommand(object target, CanExecuteRoutedEventArgs args)
        {
            MonthCalendar mcc = (MonthCalendar)target;

            int offset = 0;

            if (args.Parameter == null)
            {
                offset = MonthCalendarHelper.SubtractByMonth(DateTime.Now, mcc.VisibleMonth);     
            }
            else if (args.Parameter is DateTime)
            {
                offset = MonthCalendarHelper.SubtractByMonth((DateTime)args.Parameter, mcc.VisibleMonth);
            }

            if (offset != 0)
            {
                DateTime newValue = mcc.VisibleMonth.AddMonths(offset);

                args.CanExecute = (MonthCalendarHelper.CompareYearMonth(newValue, mcc.VisibleMonth) != 0
                                   && MonthCalendarHelper.IsWithinRange(newValue, mcc.MinDate, mcc.MaxDate));
            }
        }

        private static void OnExecuteGotoCommand(object target, ExecutedRoutedEventArgs args)
        {
            MonthCalendar mcc = (MonthCalendar)target;

            int offset = 0;
            if (args.Parameter == null)
            {
                offset = MonthCalendarHelper.SubtractByMonth(DateTime.Now, mcc.VisibleMonth);
            }
            else if (args.Parameter is DateTime)
            {
                offset = MonthCalendarHelper.SubtractByMonth((DateTime)args.Parameter, mcc.VisibleMonth);
            }

            Debug.Assert(offset != 0);
            if (offset > 0)
            {
                mcc.ScrollVisibleMonth(1, offset);
            }
            else
            {
                mcc.ScrollVisibleMonth(-1, Math.Abs(offset));
            }
        }


        private static RoutedCommand _nextCommand = null;
        private static RoutedCommand _previousCommand = null;

        /// <summary>
        /// Switch to next month
        /// </summary>
        public static RoutedCommand NextCommand
        {
            get { return _nextCommand; }
        }

        /// <summary>
        /// Switch to previous month
        /// </summary>
        public static RoutedCommand PreviousCommand
        {
            get { return _previousCommand; }
        }

        private static void OnQueryNextCommand(object target, CanExecuteRoutedEventArgs args)
        {
            MonthCalendar mcc = (MonthCalendar)target;

            args.CanExecute = MonthCalendarHelper.CompareYearMonth(mcc.VisibleMonth, mcc.MaxDate) < 0;
        }

        private static void OnExecuteNextCommand(object target, ExecutedRoutedEventArgs args)
        {
            MonthCalendar mcc = (MonthCalendar)target;
            mcc.ScrollVisibleMonth(1, 0);
        }

        private static void OnQueryPreviousCommand(object target, CanExecuteRoutedEventArgs args)
        {
            MonthCalendar mcc = (MonthCalendar)target;
            args.CanExecute = (MonthCalendarHelper.CompareYearMonth(mcc.VisibleMonth, mcc.MinDate) > 0);
        }

        private static void OnExecutePreviousCommand(object target, ExecutedRoutedEventArgs args)
        {
            MonthCalendar mcc = (MonthCalendar)target;
            mcc.ScrollVisibleMonth(-1, 0);
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Public Properties
        //
        //-------------------------------------------------------------------

        #region Public Properties

        #region SelectedDates/Date 

        /// <summary>
        /// The DependencyProperty for SelectedDate property
        /// </summary>
        public static readonly DependencyProperty SelectedDateProperty =
                DependencyProperty.Register(
                        "SelectedDate",
                        typeof(DateTime?),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (DateTime?)null,
                                FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                                new PropertyChangedCallback(OnSelectedDateChanged)),
                        new ValidateValueCallback(IsValidNullableDate));

        /// <summary>
        /// The first date in the current selection or returns null if the selection is empty
        /// </summary>
        public DateTime? SelectedDate
        {
            get { return (DateTime?)GetValue(SelectedDateProperty); }
            set { SetValue(SelectedDateProperty, value); }
        }

        private static void OnSelectedDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MonthCalendar mc = (MonthCalendar)d;

            DateTime? oldV = (DateTime?)e.OldValue;
            DateTime? newV = (DateTime?)e.NewValue;

            if (oldV != newV)
            {
                if (newV.HasValue)
                    mc.VisibleMonth = newV.Value; 
                mc.InvalidateVisibleDays();
                mc.OnSelectedDateChanged(new RoutedPropertyChangedEventArgs<DateTime?>(oldV, newV, SelectedDateChangedEvent));
            }
        }


        /// <summary>
        /// Validate input value in MonthCalendar
        /// </summary>
        /// <returns>Returns False if value isn't null and is outside CalendarDataGenerator.MinDate~MaxDate range.  Otherwise, returns True.</returns>
        private static bool IsValidNullableDate(object value)
        {
            DateTime? date = (DateTime?)value;

            return !date.HasValue || MonthCalendarHelper.IsWithinRange(date.Value, CalendarDataGenerator.MinDate, CalendarDataGenerator.MaxDate);
        }

        #endregion

        #region FirstDayOfWeek

        /// <summary>
        /// The first day of the week as displayed in the month calendar
        /// </summary>
        public DayOfWeek FirstDayOfWeek
        {
            get { return (DayOfWeek)GetValue(FirstDayOfWeekProperty); }
            set { SetValue(FirstDayOfWeekProperty, value); }
        }

        /// <summary>
        /// The DependencyProperty for FirstDayOfWeek property
        /// </summary>
        public static readonly DependencyProperty FirstDayOfWeekProperty =
                DependencyProperty.Register(
                        "FirstDayOfWeek",
                        typeof(DayOfWeek),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                CultureInfo.CurrentUICulture.DateTimeFormat.FirstDayOfWeek /* default value */,
                                new PropertyChangedCallback(OnFirstDayOfWeekChanged)),
                        new ValidateValueCallback(IsValidFirstDayOfWeek));

        private static void OnFirstDayOfWeekChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MonthCalendar)d).InvalidateVisibleDays();
        }

        private static bool IsValidFirstDayOfWeek(object value)
        {
            DayOfWeek day = (DayOfWeek)value;

            return day == DayOfWeek.Sunday
                || day == DayOfWeek.Monday
                || day == DayOfWeek.Tuesday
                || day == DayOfWeek.Wednesday
                || day == DayOfWeek.Thursday
                || day == DayOfWeek.Friday
                || day == DayOfWeek.Saturday;
        }

        #endregion FirstDayOfWeek

        #region Max/MinDate

        /// <summary>
        /// The Property for the MinDate property.
        /// </summary>
        public static readonly DependencyProperty MinDateProperty =
                DependencyProperty.Register(
                        "MinDate",
                        typeof(DateTime),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                CalendarDataGenerator.MinDate, /* The default value */
                                new PropertyChangedCallback(OnMinDateChanged)),
                        new ValidateValueCallback(IsValidDate));

        /// <summary>
        /// The min date of MonthCalendar
        /// </summary>
        public DateTime MinDate
        {
            get { return (DateTime)GetValue(MinDateProperty); }
            set { SetValue(MinDateProperty, value); }
        }

        private static void OnMinDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MonthCalendar mcc = (MonthCalendar)d;

            DateTime oldMaxDate = mcc.MaxDate;
            DateTime oldVisibleMonth = mcc.VisibleMonth;
            mcc.CoerceValue(MaxDateProperty);
            mcc.CoerceValue(VisibleMonthProperty);

            //If MaxDate, VisibleMonth hasn't been changed by CoerceValue, then 
            //we should update the IsSelectable and SelectedDates in this method
            if (MonthCalendarHelper.CompareYearMonthDay(oldMaxDate, mcc.MaxDate) == 0
                && MonthCalendarHelper.CompareYearMonth(oldVisibleMonth, mcc.VisibleMonth) == 0)
            {
                mcc.OnMaxMinDateChanged((DateTime)e.NewValue, mcc.MaxDate);
            }
        }

        /// <summary>
        /// Validate input value in MonthCalendar (MinDate, MaxDate, VisibleMonth)
        /// </summary>
        /// <returns>Returns False if value is outside CalendarDataGenerator.MinDate~MaxDate range.  Otherwise, returns True.</returns>
        private static bool IsValidDate(object value)
        {
            DateTime date = (DateTime)value;

            return (date >= CalendarDataGenerator.MinDate) &&
                    (date <= CalendarDataGenerator.MaxDate);
        }


        /// <summary>
        /// The Property for the MaxDate property.
        /// </summary>
        public static readonly DependencyProperty MaxDateProperty =
                DependencyProperty.Register(
                        "MaxDate",
                        typeof(DateTime),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                CalendarDataGenerator.MaxDate, /* The default value */
                                new PropertyChangedCallback(OnMaxDateChanged),
                                new CoerceValueCallback(CoerceMaxDate)),
                        new ValidateValueCallback(IsValidDate));

        /// <summary>
        /// The max date of MonthCalendar
        /// </summary>
        public DateTime MaxDate
        {
            get { return (DateTime)GetValue(MaxDateProperty); }
            set { SetValue(MaxDateProperty, value); }
        }

        private static object CoerceMaxDate(DependencyObject d, object value)
        {
            MonthCalendar mcc = (MonthCalendar)d;
            DateTime newValue = (DateTime)value;

            DateTime min = mcc.MinDate;
            if (newValue < min)
            {
                return min;
            }

            return value;
        }

        private static void OnMaxDateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MonthCalendar mcc = (MonthCalendar)d;

            DateTime oldVisibleMonth = mcc.VisibleMonth;
            mcc.CoerceValue(VisibleMonthProperty);

            //If VisibleMonth hasn't been changed by CoerceValue, 
            //we should update the IsSelectable and SelectedDates in this method
            if (MonthCalendarHelper.CompareYearMonth(oldVisibleMonth, mcc.VisibleMonth) == 0)
            {
                mcc.OnMaxMinDateChanged(mcc.MinDate, (DateTime)e.NewValue);
            }
        }

        /// <summary>
        /// Update the IsSelectable property of visible days and selected dates when max/min date has been changed
        /// </summary>
        /// <param name="minDate">new MinDate</param>
        /// <param name="maxDate">new MaxDate</param>
        private void OnMaxMinDateChanged(DateTime minDate, DateTime maxDate)
        {
            int count = VisibleDays.Count;
            for (int i = 0; i < count; ++i)
            {
                VisibleDays[i].IsSelectable =
                    MonthCalendarHelper.IsWithinRange(VisibleDays[i].Date, minDate, maxDate); 
            }

            if (SelectedDate.HasValue && !MonthCalendarHelper.IsWithinRange(SelectedDate.Value, minDate, maxDate))
            {
                SelectedDate = null;
            }
        }

        #endregion

        #region VisibleMonth

        /// <summary>
        /// The DependencyProperty for VisibleMonth property
        /// </summary>
        public static readonly DependencyProperty VisibleMonthProperty =
                DependencyProperty.Register(
                        "VisibleMonth",
                        typeof(DateTime),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                DateTime.Today /* default value */,
                                new PropertyChangedCallback(OnVisibleMonthChanged),
                                new CoerceValueCallback(CoerceVisibleMonth)),
                        //Note: Though only year/month work on VisibleMonth, we'll still compare its value with CalendarDataGenerator.Max/MinDate
                        new ValidateValueCallback(IsValidDate)); 

        /// <summary>
        /// The first visible month
        /// </summary>
        /// <remarks>
        /// Only the Year and Month field is used, not guarantee the day is 1!
        /// </remarks>
        public DateTime VisibleMonth
        {
            get { return (DateTime)GetValue(VisibleMonthProperty); }
            set { SetValue(VisibleMonthProperty, value); }
        }


        private static object CoerceVisibleMonth(DependencyObject d, object value)
        {
            MonthCalendar mcc = (MonthCalendar)d;
            DateTime newValue = (DateTime)value;

            DateTime min = mcc.MinDate;
            if (newValue < min)
            {
                return min;
            }

            DateTime max = mcc.MaxDate;
            if (newValue > max)
            {
                return max;
            }

            return newValue;
        }

        private static void OnVisibleMonthChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MonthCalendar mcc = (MonthCalendar)d;
            DateTime oldDate = (DateTime)e.OldValue;
            DateTime newDate = (DateTime)e.NewValue;

            //oldDate != newDate in Year/Month field
            if (MonthCalendarHelper.CompareYearMonth(oldDate, newDate) != 0)
            {
                mcc.InvalidateVisibleDays();
                mcc.OnVisibleMonthChanged(new RoutedPropertyChangedEventArgs<DateTime>(oldDate, newDate, VisibleMonthChangedEvent));
            }
        }

        #endregion VisibleMonth

        #region ShowsTitle/WeekNumbers/DayHeader Properties

        /// <summary>
        /// The DependencyProperty for ShowsTitle property
        /// </summary>
        public static readonly DependencyProperty ShowsTitleProperty =
                DependencyProperty.Register(
                        "ShowsTitle",
                        typeof(bool),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));

        /// <summary>
        /// Indicating whether the control displays the title or not
        /// </summary>
        public bool ShowsTitle
        {
            get { return (bool)GetValue(ShowsTitleProperty); }
            set { SetValue(ShowsTitleProperty, BooleanBoxes.Box(value)); }
        }

        /// <summary>
        /// The DependencyProperty for ShowsWeekNumbers property
        /// </summary>
        public static readonly DependencyProperty ShowsWeekNumbersProperty =
                DependencyProperty.Register(
                        "ShowsWeekNumbers",
                        typeof(bool),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(BooleanBoxes.FalseBox));

        /// <summary>
        /// Indicating whether the control displays the week numbers or not
        /// </summary>
        public bool ShowsWeekNumbers
        {
            get { return (bool)GetValue(ShowsWeekNumbersProperty); }
            set { SetValue(ShowsWeekNumbersProperty, BooleanBoxes.Box(value)); }
        }

        /// <summary>
        /// The DependencyProperty for ShowsDayHeaders property
        /// </summary>
        public static readonly DependencyProperty ShowsDayHeadersProperty =
                DependencyProperty.Register(
                        "ShowsDayHeaders",
                        typeof(bool),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(BooleanBoxes.TrueBox));

        /// <summary>
        /// Indicating whether the control displays the day header or not
        /// </summary>
        public bool ShowsDayHeaders
        {
            get { return (bool)GetValue(ShowsDayHeadersProperty); }
            set { SetValue(ShowsDayHeadersProperty, BooleanBoxes.Box(value)); }
        }

        #endregion

        #region DayTemplate/DayTemplateSelector/DayContainerStyle/DayContainerStyleSelector

        /// <summary>
        /// The DependencyProperty for the DayTemplate property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty DayTemplateProperty =
                DependencyProperty.Register(
                        "DayTemplate",
                        typeof(DataTemplate),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (DataTemplate)null,
                                new PropertyChangedCallback(OnDayTemplateChanged)));

        /// <summary>
        /// DayTemplate is the template that describes how to convert Items into UI elements.
        /// </summary>
        /// <remarks>
        /// DayTemplate must bind with Date, so it can be updated when scroll month
        /// here is a sample:
        /// &lt;DataTemplate x:Key="dayTemplate"&gt;
        ///     &lt;TextBlock Text="{Binding Date}"/&gt;
        /// &lt;/DataTemplate&gt;
        /// </remarks>
        public DataTemplate DayTemplate
        {
            get { return (DataTemplate)GetValue(DayTemplateProperty); }
            set { SetValue(DayTemplateProperty, value); }
        }

        /// <summary>
        /// Called when DayTemplateProperty is invalidated on "d."
        /// </summary>
        private static void OnDayTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MonthCalendar mcc = (MonthCalendar)d;
            mcc.RefreshDayTemplate();
        }

        /// <summary>
        /// The DependencyProperty for the DayTemplateSelector property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty DayTemplateSelectorProperty =
                DependencyProperty.Register(
                        "DayTemplateSelector",
                        typeof(DataTemplateSelector),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (DataTemplateSelector)null,
                                new PropertyChangedCallback(OnDayTemplateSelectorChanged)));

        /// <summary>
        /// DayTemplateSelector allows the app writer to provide custom template selection logic
        /// for a template to apply to each item.
        /// </summary>
        public DataTemplateSelector DayTemplateSelector
        {
            get { return (DataTemplateSelector)GetValue(DayTemplateSelectorProperty); }
            set { SetValue(DayTemplateSelectorProperty, value); }
        }

        /// <summary>
        /// Called when DayTemplateSelectorProperty is invalidated on "d."
        /// </summary>
        private static void OnDayTemplateSelectorChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            MonthCalendar mcc = (MonthCalendar)d;
            mcc.RefreshDayTemplate();
        }

        /// <summary>
        /// The DependencyProperty for the DayContainerStyle property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty DayContainerStyleProperty =
                DependencyProperty.Register(
                        "DayContainerStyle",
                        typeof(Style),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (Style)null,
                                new PropertyChangedCallback(OnDayContainerStyleChanged)));

        /// <summary>
        /// 
        /// </summary>
        public Style DayContainerStyle
        {
            get { return (Style)GetValue(DayContainerStyleProperty); }
            set { SetValue(DayContainerStyleProperty, value); }
        }

        /// <summary>
        /// Called when DayContainerStyleProperty is invalidated on "d."
        /// </summary>
        private static void OnDayContainerStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MonthCalendar)d).RefreshDayTemplate();
        }

        /// <summary>
        ///     The DependencyProperty for the DayContainerStyleSelector property.
        ///     Flags:              none
        ///     Default Value:      null
        /// </summary>
        public static readonly DependencyProperty DayContainerStyleSelectorProperty =
                DependencyProperty.Register(
                        "DayContainerStyleSelector",
                        typeof(StyleSelector),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (StyleSelector)null,
                                new PropertyChangedCallback(OnDayContainerStyleChanged)));

        /// <summary>
        ///     DayContainerStyleSelector allows the application writer to provide custom logic
        ///     to choose the style to apply to each generated day element.
        /// </summary>
        /// <remarks>
        ///     This property is ignored if <seealso cref="DayContainerStyle"/> is set.
        /// </remarks>
        public StyleSelector DayContainerStyleSelector
        {
            get { return (StyleSelector)GetValue(DayContainerStyleSelectorProperty); }
            set { SetValue(DayContainerStyleSelectorProperty, value); }
        }

        #endregion

        #region TitleStyle/DayHeaderStyle/WeekNumberStyle

        /// <summary>
        /// The DependencyProperty for the TitleStyle property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty TitleStyleProperty =
                DependencyProperty.Register(
                        "TitleStyle",
                        typeof(Style),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (Style)null));

        /// <summary>
        /// TitleStyle property
        /// </summary>
        public Style TitleStyle
        {
            get { return (Style)GetValue(TitleStyleProperty); }
            set { SetValue(TitleStyleProperty, value); }
        }

        /// <summary>
        /// The DependencyProperty for the DayHeaderStyle property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty DayHeaderStyleProperty =
                DependencyProperty.Register(
                        "DayHeaderStyle",
                        typeof(Style),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (Style)null));

        /// <summary>
        /// DayHeaderStyle property
        /// </summary>
        public Style DayHeaderStyle
        {
            get { return (Style)GetValue(DayHeaderStyleProperty); }
            set { SetValue(DayHeaderStyleProperty, value); }
        }

        /// <summary>
        /// The DependencyProperty for the WeekNumberStyle property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty WeekNumberStyleProperty =
                DependencyProperty.Register(
                        "WeekNumberStyle",
                        typeof(Style),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (Style)null));

        /// <summary>
        /// WeekNumberStyle property
        /// </summary>
        public Style WeekNumberStyle
        {
            get { return (Style)GetValue(WeekNumberStyleProperty); }
            set { SetValue(WeekNumberStyleProperty, value); }
        }

        #endregion

        #region Previous/NextButtonStyle

        /// <summary>
        /// The DependencyProperty for the PreviousButtonStyle property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty PreviousButtonStyleProperty =
                DependencyProperty.Register(
                        "PreviousButtonStyle",
                        typeof(Style),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (Style)null, new PropertyChangedCallback(OnPreviousButtonStyleChanged)));

        /// <summary>
        /// PreviousButtonStyle property
        /// </summary>
        public Style PreviousButtonStyle
        {
            get { return (Style)GetValue(PreviousButtonStyleProperty); }
            set { SetValue(PreviousButtonStyleProperty, value); }
        }

        private static void OnPreviousButtonStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MonthCalendar)d).RefreshPreviousButtonStyle();
        }

        /// <summary>
        /// The DependencyProperty for the NextButtonStyle property.
        /// Flags:              none
        /// Default Value:      null
        /// </summary>
        public static readonly DependencyProperty NextButtonStyleProperty =
                DependencyProperty.Register(
                        "NextButtonStyle",
                        typeof(Style),
                        typeof(MonthCalendar),
                        new FrameworkPropertyMetadata(
                                (Style)null, new PropertyChangedCallback(OnNextButtonStyleChanged)));

        /// <summary>
        /// NextButtonStyle property
        /// </summary>
        public Style NextButtonStyle
        {
            get { return (Style)GetValue(NextButtonStyleProperty); }
            set { SetValue(NextButtonStyleProperty, value); }
        }

        private static void OnNextButtonStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((MonthCalendar)d).RefreshNextButtonStyle();
        }

        #endregion


        #endregion

        //------------------------------------------------------
        //
        //  Protected Methods
        //
        //------------------------------------------------------

        #region Protected Methods

        /// <summary>
        /// Raise VisibleMonthChanged event.
        /// </summary>
        /// <param name="e">RoutedPropertyChangedEventArgs contains the old and new value.</param>
        protected virtual void OnVisibleMonthChanged(RoutedPropertyChangedEventArgs<DateTime> e)
        {
            RaiseEvent(e);
        }

        /// <summary>
        /// Raise SelecteDateChanged event.
        /// </summary>
        protected virtual void OnSelectedDateChanged(RoutedPropertyChangedEventArgs<DateTime?> e)
        {
            RaiseEvent(e);
        }

        /// <summary>
        /// This is the method that responds to the PreviewKeyDown event.
        /// </summary>
        /// <param name="e">Event Arguments</param>
        /// <remarks>
        /// Override OnPreviewKeyDown isn't recommended for Control Author, it's reserved for customer
        /// Because MonthCalenarContainer already handles the PageUp/PageDown/Home/End, we have to use Preview here.
        /// </remarks>
        protected override void OnPreviewKeyDown(KeyEventArgs e)
        {
            e.Handled = true; 
            switch (e.Key)
            {
                case Key.PageUp:
                    PreviousCommand.Execute(null, this);
                    break;
                case Key.PageDown:
                    NextCommand.Execute(null, this);
                    break;
            }
            if (SelectedDate != null)
            {
                DateTime date = SelectedDate.Value;
                switch (e.Key)
                {
                    case Key.Up:
                        SelectedDate = date.AddDays(-7);
                        break;
                    case Key.Down:
                        SelectedDate = date.AddDays(7);
                        break;
                    case Key.Left:
                        SelectedDate = date.AddDays(-1);
                        break;
                    case Key.Right:
                        SelectedDate = date.AddDays(1);
                        break;
                }

            }
        }

        #endregion

        //-------------------------------------------------------------------
        //
        //  Private Methods
        //
        //-------------------------------------------------------------------

        #region Private Methods

        /// <summary>
        /// Generate the visible days collection based on the input firstdate, lastdate and firstdayofweek
        /// </summary>
        private ObservableCollection<CalendarDate> CreateVisibleDaysCollection(DateTime firstDate, DateTime lastDate, DayOfWeek firstDayOfWeek)
        {
            DateTime leadingDate = CalendarDataGenerator.CalculateLeadingDate(firstDate, firstDayOfWeek);
            DateTime trailingDate = CalendarDataGenerator.CalculateTrailingDate(firstDate, lastDate, firstDayOfWeek);
            int totalDay = trailingDate.Subtract(leadingDate).Days + 1;

            ObservableCollection<CalendarDate> collection = new ObservableCollection<CalendarDate>();
            for (int i = 0; i < totalDay; ++i)
            {
                CalendarDate cdate = new CalendarDate(leadingDate.AddDays(i));
                cdate.IsOtherMonth = cdate.Date < FirstDate || cdate.Date > LastDate;
                cdate.IsSelectable = MonthCalendarHelper.IsWithinRange(cdate.Date, MinDate, MaxDate);

                collection.Add(cdate);
            }

            return collection;
        }

        /// <summary>
        /// Invalidate the visible days when switch month
        /// </summary>
        /// <param name="scrollChange"></param>
        private void InvalidateVisibleDays()
        {
            if (_mccContainer != null)
            {
                ObservableCollection<CalendarDate> newVisibleDays = CreateVisibleDaysCollection(FirstDate, LastDate, FirstDayOfWeek);
                Debug.Assert(newVisibleDays.Count == 42);
                VisibleDays = newVisibleDays;
                _mccContainer.ItemsSource = newVisibleDays;
                _mccContainer.SelectedItem = SelectedDate.TrySC(sd=>newVisibleDays.SingleOrDefault(c => c.Date == sd));
            }
        }


        /// <summary>
        /// Scroll the current visible month of MothCalendar based on delta and direction
        /// </summary>
        /// <param name="direction">1:increase; -1: decrease</param>
        /// <param name="delta">how many months should be scrolled, 0 means use the default value</param>
        private void ScrollVisibleMonth(int direction, int delta)
        {
            //NOTE:
            // To read the graph below, please use fixed width font.
            //
            // Date range:                              Min                               Max
            //                                           |--------------------------------|
            //                                           .                                .
            // Valid cases:                              .                                .
            //                                           .                                .
            // Case 1:                                   .     [..................]       .
            //                                           .  FstVsM                        .
            //                                           .                                .
            // Case 2:                                   [..................]             .
            //                                        FstVsM                              .
            //                                           .                                .
            // Case 3:                                   .             [..................]
            //                                           .          FstVsM                .
            //                                           .                                .
            // Case 4:                                   [.........................................]
            //                                        FstVsM                              .
            //                                           .                                .
            //                                           .                                .
            // Case 5:                                   .             [.......................]
            //                                           .           FstVsM               .
            //                                           .                                .
            // Valid initial but no returnable cases:    .                                .
            //                                           .                                .
            // Case 6:                      [..................]                          .
            //                           FstVsM                                           .
            //                                           .                                .
            // Case 7:                                   .                                .       [..................]
            //                                           .                                .    FstVsM
            //
            // Invalid case(s)
            //                                           .                                .
            // Case 8:                          [.........................................]
            //                               FstVsM

            int monthCount = 1;

            if (delta == 0)
            {
                delta = monthCount;
            }

            DateTime firstMonth = VisibleMonth;
            bool checkFirstMonth = false;

            // Scroll to the previous page
            if (direction == -1)
            {
                if (MonthCalendarHelper.CompareYearMonth(VisibleMonth, MinDate) > 0) // case 1/3/5
                {
                    checkFirstMonth = true;
                    try
                    {
                        firstMonth = VisibleMonth.AddMonths(delta * direction);
                    }
                    catch (ArgumentOutOfRangeException)
                    {
                        firstMonth = MinDate; // case 2/4
                    }
                }
            }
            // Scroll to the next page
            else
            {
                try
                {
                    if (MonthCalendarHelper.CompareYearMonth(LastDate, MaxDate) < 0) // case 1-4
                    {
                        firstMonth = VisibleMonth.AddMonths(delta * direction);
                        DateTime lastMonth = firstMonth.AddMonths(monthCount - 1);

                        // if lastMonth is greater than MaxDate, scroll back to the appropriate position
                        if (MonthCalendarHelper.CompareYearMonth(lastMonth, MaxDate) > 0) // case 5
                        {
                            checkFirstMonth = true;
                            firstMonth = MaxDate.AddMonths(-(monthCount - 1)); // case 3
                        }
                    }
                }
                catch (ArgumentOutOfRangeException)
                {
                    checkFirstMonth = true;
                    firstMonth = MaxDate.AddMonths(-(monthCount - 1)); // case 3
                }
            }

            // check in case the firstMonth is less than MinDate
            if (checkFirstMonth && MonthCalendarHelper.CompareYearMonth(firstMonth, MinDate) < 0) // case 8
            {
                firstMonth = MinDate; // change to case 4
            }

            VisibleMonth = firstMonth;
        }

  
      

        /// <summary>
        /// Refresh the ItemTemplate/ItemTemplateSelector/ItemContainerStyle if DayTemplate/DayTemplateSelecotr/DayContainerStyle is set
        /// </summary>
        private void RefreshDayTemplate()
        {
            if (_mccContainer != null)
            {
                //if both DayTemplate/Selector is null, use the default DayTemplate
                if (DayTemplate == null && DayTemplateSelector == null)
                {
                    if (s_MonthCalendarDayTemplate == null)
                    {
                        s_MonthCalendarDayTemplate = new DataTemplate();
                        FrameworkElementFactory txt = new FrameworkElementFactory(typeof(TextBlock));
                        txt.SetBinding(TextBlock.TextProperty, new Binding("Date.Day"));
                        s_MonthCalendarDayTemplate.VisualTree = txt;
                    }

                    _mccContainer.ItemTemplateSelector = null;
                    _mccContainer.ItemTemplate = s_MonthCalendarDayTemplate;
                }
                else
                {
                    _mccContainer.ItemTemplate = DayTemplate;
                    _mccContainer.ItemTemplateSelector = DayTemplateSelector;
                }

                //SetFlag(Flags.IsVisibleDaysUpdated, true);
            }
        }

        private void RefreshPreviousButtonStyle()
        {
            ButtonBase previousButton = GetTemplateChild(c_PreviousButtonName) as ButtonBase;
            if (previousButton != null)
            {
                if (PreviousButtonStyle == null)
                {
                    if (_defaultPreviousButtonStyle == null)
                    {
                        _defaultPreviousButtonStyle = FindResource(new ComponentResourceKey(typeof(MonthCalendar), "PreviousButtonStyleKey")) as Style;
                    }
                    previousButton.Style = _defaultPreviousButtonStyle;
                }
                else
                {
                    previousButton.Style = PreviousButtonStyle;
                }
            }
        }

        private void RefreshNextButtonStyle()
        {
            ButtonBase nextButton = GetTemplateChild(c_NextButtonName) as ButtonBase;
            if (nextButton != null)
            {
                if (NextButtonStyle == null)
                {
                    if (_defaultNextButtonStyle == null)
                    {
                        _defaultNextButtonStyle = FindResource(new ComponentResourceKey(typeof(MonthCalendar), "NextButtonStyleKey")) as Style;
                    }
                    nextButton.Style = _defaultNextButtonStyle;
                }
                else
                {
                    nextButton.Style = NextButtonStyle;
                }
            }
        }
     
        #region Selection

        /// <summary>
        /// Get the CalendarDate by date
        /// </summary>
        /// <returns>null if the date exceeds First/LastVisibleDate</returns>
        private CalendarDate GetCalendarDateByDate(DateTime date)
        {
            TimeSpan ts = date - FirstVisibleDate;
            return VisibleDays.Count > ts.Days && ts.Days >= 0 ? VisibleDays[ts.Days] : null;
        }

   
        /// <summary>
        /// Update the selected dates status when user changes it by UI
        /// </summary>
        private void OnContainerSelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            if (_mccContainer.TemplatedParent == null || e.OriginalSource != _mccContainer)
                return;

            if (_mccContainer.SelectedItem != null)
                SelectedDate = ((CalendarDate)_mccContainer.SelectedItem).Date;
        }


        #endregion

        #endregion

        //-------------------------------------------------------------------
        //
        //  Private Fields
        //
        //-------------------------------------------------------------------

        #region Private Fields

        private MonthCalendarContainer _mccContainer;

        private DateTime FirstVisibleDate { get { return VisibleDays[0].Date; } }

        private DateTime LastVisibleDate { get { return VisibleDays[VisibleDays.Count - 1].Date; } }

        /// <summary>
        /// first day of the first currently visible month
        /// </summary>
        private DateTime FirstDate
        {
            get { return new DateTime(VisibleMonth.Year, VisibleMonth.Month, 1); }
        }

        /// <summary>
        /// last day of the last currently visible month
        /// </summary>
        private DateTime LastDate
        {
            get { return FirstDate.AddMonths(1).AddDays(-1); }
        }

        private ObservableCollection<CalendarDate> VisibleDays
        {
            get 
            {
                if (_visibleDays == null)
                {
                    _visibleDays = CreateVisibleDaysCollection(FirstDate, LastDate, FirstDayOfWeek);
                }
                return _visibleDays;
            }
            set 
            {
                _visibleDays = value;
            }
        }

        private ObservableCollection<CalendarDate> _visibleDays;


        private Style _defaultPreviousButtonStyle, _defaultNextButtonStyle;
        private static DataTemplate s_MonthCalendarDayTemplate;

        // Part name used in the style. The class TemplatePartAttribute should use the same name
        private const string c_VisibleDaysHostTemplateName = "PART_VisibleDaysHost";
        private const string c_PreviousButtonName = "PART_PreviousButton";
        private const string c_NextButtonName = "PART_NextButton";

        private const string c_exRangeActionsNotSupported = "Range actions are not supported.";

        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Signum Software
Spain Spain
I'm Computer Scientist, one of the founders of Signum Software, and the lead developer behind Signum Framework.

www.signumframework.com

I love programming in C#, Linq, Compilers, Algorithms, Functional Programming, Computer Graphics, Maths...

Comments and Discussions