Click here to Skip to main content
Licence Ms-PL
First Posted 4 Nov 2009
Views 15,165
Downloads 1,302
Bookmarked 12 times

PersianDate and Some WPF Controls For It

By Arash Sahebolamri | 5 Oct 2010
A type for holding Persian date values, and two WPF controls for working with Persian dates

1

2
1 vote, 5.0%
3
2 votes, 10.0%
4
17 votes, 85.0%
5
4.85/5 - 20 votes
1 removed
μ 4.71, σa 0.92 [?]
PersianDateAndWpfDemo

Introduction

The source code consists of three projects: PersianDate, which is a type (structure actually) for holding values of the Persian calendar; PersianDateControls, which has two WPF controls for Persian calendar: PersianCalendar and PersianDatePicker (these controls are pretty much like the ones found in the WPF Toolkit: Calendar and DatePicker; these two controls use the type PersianDate to work with values of the Persian calendar); and the third project is a simple demo that demonstrates using these controls.

About the Persian Calendar

The Persian calendar is a sonar calendar, like Gregorian calendar, but there are some differences. One is that the origins are different, and the Persian calendar's origin is about 621 years after Gregorian calendar's; another one is that Persian calendar's first day of year is March 21; and probably the most important one is that the average length of a Persian calendar year is different from that of a Gregorian calendar's: the Persian calendar has 8 leap years (that is a year that has an extra day than normal years) in each 33 years, whereas the Gregorian calendar has 8 leap years in each 32 years. This little difference means that Persian dates cannot be calculated directly from Gregorian dates.

The PersianDate Structure

The PersianDate structure stores dates of the Persian calendar. It is somehow similar to the DateTime structure in the .NET Framework Class Library, except that PersianDate only stores the date, and it doesn't store the time. This structure has only one field, which stores the number of days passed after the first day of the first year of the Persian calendar(1/1/1):

uint n; //the only field, stores the number of days passed 1/1/1 

The calculations for the year, month, and day are based on this single value. The private yearMonthDay() method takes this number and returns the day, month, and year represented by this number. In order to do so, dates are divided into some groups:

const int period33y = 365 * 33 + 8;

const int p33p1 = 366;
const int p33p2 = 365 * 20 + 4;
const int p33p3 = 366;
const int p33p4 = 365 * 11 + 2;

The first constant (period33y) is the number of days in each 33 years; this is divided into four groups: in each one, the number of days in a year are the same. For example, if n % period33y is 400, these categories mean that the date is in the p33p2 group since it is > p33p1 and <= p33p1+p33p2, and that means that the date is not in a leap year, and by doing some relatively simple calculations, the year of the date can be extracted. After that, the day and month parts of the date are extracted in this method.

These constants are also used when calculating n from the year, month, and day. The days() method does this. This method is used in one of the constructors of the structure which takes the year, month, and day as arguments.

Some Notable Points About PersianDate

The PersianDate is a structure, not a class, because of performance reasons. As mentioned before, this structure has only one 4 byte field, so it is much more rational to make it a structure than a class. Since access to objects of classes are indirect, and a reference to an object is at least 4 bytes long, which is the size of the data itself, it doesn't seem reasonable to make this type a reference type.

The type is immutable, and this is the recommended way of making structures. The reason is that mutable structures have weird behaviours in certain scenarios. For example, suppose that the type was mutable, and there was an AddDays() method which would add the number of days given as the parameter to the instance. Now suppose that there is a Foo class which has a field of type PersianDate named DateField:

Foo foo=new Foo();
foo.DateField=new PersianDate(1376,2,22);
foo.DateField.AddDays(12);
System.Console.WriteLine(foo.dateField.ToString());

The output would be 1376/3/2, which is what you might have expected. Now, suppose that this Foo class also had a property of type PersianDate, named dateProperty, and let's say it was auto implemented:

class Foo{
    public PersianDate DateProperty{get; set;}
    ...
}

If you wrote a similar code to the one you had written for DateField...

Foo foo=new Foo();
foo.DateProperty=new PersianDate(1376,2,22);
foo.DateProperty.AddDays(12);
System.Console.WriteLine(foo.dateProperty.ToString());

...the output would be 1376/2/22! The reason is that since PersianDate is a value type, the getter of dateProperty would return a copy of the value stored, and the AddDays method would mutate this copy, and not the actual backing field; and that is why the value of the property is not changed.

So that is why PersianDate is immutable.

The PersianCalendar Class

PersianCalendar is a WPF user control. In WPF, user controls derive from the System.Windows.Controls.UserControl base class. This WPF control represents the Persian calendar, very much like the Calendar control (System.Windows.Controls.Calendar) in the WPF Toolkit. Just like WPF's Calendar, it has a property called DisplayMode which can be used to choose how the calendar is displayed: whether it displays years in a decade, months in a year, or days in a month.

This control uses the PersianDate type to work with the Persian calendar, so properties like DisplayDate or SelectedDate are of this type.

Some stuff about the PersianCalendar class: I have used WPF's UniformGrid control as the container control to arrange the date buttons in PersianCalendar:

<UniformGrid Margin="3,26,3,2" Name="monthUniformGrid" 
  Rows="7" Columns="7"  FlowDirection="RightToLeft"/>
<UniformGrid Margin="3,26,3,2" Name="yearUniformGrid" 
  Columns="3" Rows="4" FlowDirection="RightToLeft"/>
<UniformGrid Margin="3,26,3,2" Name="decadeUniformGrid" 
  Columns="3" Rows="4" FlowDirection="RightToLeft"/>

Each one is used for one of DisplayMode's three values. For example, when the DisplayMode is set to Month, monthUniformGrid is displayed and the other two are collapsed (that is, they are hidden and they don't reserve space):

private void setMonthMode()
{
    this.decadeUniformGrid.Visibility = 
      this.yearUniformGrid.Visibility = Visibility.Collapsed;
    this.monthUniformGrid.Visibility = Visibility.Visible;
    ...
}

Customizing the Appearance of Controls Using Styles and Templates

In order for the PersianCalendar to work, these UniformGrids must be filled with controls that display dates (or months or decades). Label is not a good choice because it doesn't have a Click event, so I have used Button; but Button's appearance didn't seem very pleasing for this purpose, so I have used Styles and Templates to change it:

<Style x:Key="InsideButtonsStyle" TargetType="Button">
    ...
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border x:Name="Border" CornerRadius="2" 
                         BorderThickness="0" 
                         Background="{TemplateBinding Background}" 
                         BorderBrush="{StaticResource NormalBorderBrush}">
                    <ContentPresenter Margin="2" 
                       HorizontalAlignment="Center" 
                       VerticalAlignment="Center" 
                       RecognizesAccessKey="True"/>
                </Border>
                <ControlTemplate.Triggers>
                    ...
                    <Trigger Property="IsMouseOver" Value="true">
                        <Setter TargetName="Border" 
                            Property="Background" 
                            Value="{StaticResource HoverBackgroundBrush}" />
                        <Setter  Property="Foreground" 
                            Value="{StaticResource HoverForegroundBrush}" />
                    </Trigger>
                    <Trigger Property="IsMouseOver" Value="false">
                        <Setter  Property="Foreground" 
                           Value="{StaticResource HoverForegroundBrush}" />
                    </Trigger>
                    ...
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Inside the ControlTemplate, Triggers are used to alter the appearance of the buttons when, for example, they are hovered by the mouse, etc.

Something interesting about this template is the use of Template Binding to bind the value of the Button's Background property to the Background property of the template's visual tree element, Border.

This style, along with the template defined in it, is applied to buttons when they are created:

Button newControl()
{
    var element = new Button
    {
        ...
        Style = (Style)this.FindResource("InsideButtonsStyle"),
        ...
    };
    return element;
}

Dependency Properties and Routed Events

All the properties defined in the PersianCalendar class are Dependency Properties, and the events are RoutedEvents; this is WPF's recommended style for making user controls. Making the properties dependency properties requires that no extra code be put in their getter and setter accessors, so PropertyMetaData is used whenever any validation or mutation of the PersianCalendar object is required. For example, consider the SelectedDate property:

public PersianDate SelectedDate
{
    get { return (PersianDate)GetValue(SelectedDateProperty); }
    set { SetValue(SelectedDateProperty, value); }
}               
public static readonly DependencyProperty SelectedDateProperty;

As you can see, all the setter does is just set the value for the backing field, which is of type DependencyProperty, of course. The extra logic is put into the property's metadata (by using lambda expressions in my code) in the static constructor of the class:

static PersianCalendar()
{
    ...
    PropertyMetadata selectedDateMetaData = new PropertyMetadata(
    (DependencyObject d, DependencyPropertyChangedEventArgs e) =>
    {
        PersianCalendar pc = d as PersianCalendar;
        pc.selectedDateCheck((PersianDate)e.OldValue);
    }
    );
    SelectedDateProperty=
        DependencyProperty.Register("SelectedDate", 
          typeof(PersianDate), typeof(PersianCalendar), selectedDateMetaData);
    ...
}

The PersianDatePicker Class

It doesn't do any magic really. This control just uses a TextBox and a PersianCalendar, and holds the PersianCalendar in a Popup control to display it in a different window whenever the corresponding button is clicked. There is one thing notable about this class though, and that is using Data Binding for connecting the properties of this class to those of the PersianCalendar. Here is the code that demonstrates using this feature for binding the SelectedDate property:

Binding selectedDateBinding = new Binding
{
    Source = this.persianCalendar,
    Path = new PropertyPath("SelectedDate"),
    Mode = BindingMode.TwoWay,
};
this.SetBinding(SelectedDateProperty, selectedDateBinding);

Using this technique has relieved me from having to write all the messy code to keep these properties in sync.

How to Use the Code

If you just want to use PersianDate, you can either add its project to your solution, or build it and reference the assembly; and if you want to use the WPF controls, you should either add both projects (PersianDate and PersianDateControls) to your solution, or build them both and reference them both (since PersianDateControls uses PersianDate).

History

  • Ver. 1.1
    • Removed PersianDateControl's dependency on the WPFToolkit assembly (actually, it only used the CalendarMode enum from that assembly, which is now in the CalendarMode.cs code file in the project), plus some other minor changes (note that you still need WPFToolkit if you want to build and run the demo project).
  • Ver 1.2
    • Made a few minor tweaks, including solving the problem with tab orders.
      Note that this version is not compatible with the previous version, because TodayBackGround is changed to TodayBackground, and SelectedDateBackGround is changed to SelectedDateBackground.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

About the Author

Arash Sahebolamri



Iran (Islamic Republic Of) Iran (Islamic Republic Of)

Member


Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
Question.net 4 Pinmemberiranpcl12:14 30 Jan '12  
GeneralMy vote of 5 PinmemberShahin Khorshidnia5:47 26 Jan '12  
Questionسپاس فراوان بابت کنترل تقویم فارسی Pinmembersarshogh_eng@yahoo.com19:42 28 Dec '11  
GeneralMy vote of 5 PinmemberDeviser21:49 9 Nov '11  
Questionvery good PinmemberMember 390827821:10 2 Nov '11  
GeneralMy vote of 5 PinmemberShahin Khorshidnia22:46 24 Oct '11  
QuestionMy vote of 5 [modified] Pinmembermojmos21:33 27 Sep '11  
GeneralMy vote of 5 PinmemberDariush Tasdighi23:24 19 Mar '11  
Generalusing dll files in web and win form Pinmembera_motallebi23:53 11 Oct '10  
GeneralRe: using dll files in web and win form PinmemberArash Sahebolamri1:42 12 Oct '10  
GeneralRe: using dll files in web and win form Pinmembera_motallebi21:15 12 Oct '10  
GeneralRe: using dll files in web and win form PinmemberArash Sahebolamri23:19 12 Oct '10  
GeneralChange PersianDateControls.dll PinmemberDavood_Amega7:04 29 Sep '10  
GeneralThanks Pinmemberbabakzawari10:10 5 Jun '10  
GeneralRe: Thanks PinmemberArash Sahebolamri0:52 28 Jul '10  
GeneralGood Job PinmemberDr TJ13:31 22 Dec '09  
Questionany win form version? PinmemberAli Poostchian19:47 9 Nov '09  
AnswerRe: any win form version? PinmemberArash Sahebolamri10:25 11 Nov '09  
AnswerRe: any win form version? PinmemberHamid Reza Mohammadi20:33 13 Nov '09  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120210.1 | Last Updated 5 Oct 2010
Article Copyright 2009 by Arash Sahebolamri
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid