Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Farsi Library FX

0.00/5 (No votes)
9 Nov 2007 3  
WPF custom controls to work with Dates in Culture specific calendars, supporting Farsi (Hejri Shamsi), Arabic (Lunar Hijri) and Gregorian calendars.
Screenshot - Themes

WPF is the latest Graphical Engine of .NET Framework 3, and in my idea is one of the best UI Frameworks so far, considering the rich support for Right-To-Left languages which is built inside WPF. Since it's a brand new technology, there are very few resources on how to build Custom Controls, how to enable Themes and Skins and what considerations are to be taken. In this article, I'll be creating a MonthView and a DatePicker Custom Control to work with Dates in various cultures. These controls will render correctly under all known system Themes (Classic, Aero, Luna, Royale) and there's a sample to show you how to change the Look and Feel of the component completely. Please note that to run and use these components, you'll need .NET Framework 3 installed, which installs on Windows Vista/Windows XP/Windows Server 2003 only.

To get the concept of culture-enabled components see my other article here.

Notice: I've used Visual Studio 2008 Beta 2 (Orcas) on this project. Further versions of the IDE/Framework may or may not be compatible with the provided controls.

Design-Time Integration

Because Cider (Visual Studio Designer for WPF applications) is just in version 1, there's a limited design time functionality which could be incorporated into custom components. For the current version, the design-time experience is as limited as the property pane of VS.NET. Hopefully I'll add more features in the upcoming versions.

First Things First, Selecting a Base Class

On the way to creating a new control in WPF, you first need to select a base class. Unlike control development in .NET 2 / WinForms, you don't have to subclass a control in order to change the way it looks. In WPF, look and feel of a control is defined in XAML files, and the logic it has is defined in the control itself (code behind file) So, if you need to change the way a control is used, you can easily change the control's Style or to customize it even more, you can change its Template, or Templates of some parts (I'll show you how, later). If you're creating a control from scratch, you probably should be using Control - should not be mixed with it's counterpart in WinForms namespace - which resides in System.Windows.Controls namespace. The important point when creating a custom control is to override the default (base) style of the control in order to create a new look and feel. You can achieve this by adding the following code to your static constructor:

static FXMonthView()
{
   DefaultStyleKeyProperty.OverrideMetadata(typeof(FXMonthView),
        new FrameworkPropertyMetadata(typeof(FXMonthView)));
}

You may also override other settings inherited from its base class, such as TabStop, keyboard Focus, Hit Test visibility, etc. As is the case here, we've also overridden the TabStop property.

static FXMonthView
{
   DefaultStyleKeyProperty.OverrideMetadata(typeof(FXMonthView),
        new FrameworkPropertyMetadata(typeof(FXMonthView)));
   IsTabStopProperty.OverrideMetadata(typeof(FXMonthView),
        new FrameworkPropertyMetadata(false));
}

Creating the Default Look

Although, it is being said that all controls in WPF are lookless, the developer should provide a default look for a newly created control. You can do this in a ResourceDictionary (XAML file), the default being named Generic.Xaml when creating a custom control library. When creating a look for a control, we have to create a new style for the control. Notice the Key and TargetType properties here:

<Style x:Key="{x:Type Win:FXMonthView}" TargetType="{x:Type Win:FXMonthView}">

In case of our calendar control, the main layout is a Grid control. In the header part, we have some Labels to display Month/Year information and Repeat Buttons to navigate to previous and next month and year. In the middle we have a ListBox control with modified layout that displays days of the month. In the footer part, we have Today and Empty buttons. To make the customization tasks a little easier, there are some Style properties defined in FXMonthView control for various parts. This way you can customize each part by assigning a new style to the related property. For example, in order to change the style of the footer buttons (e.g. Today, Empty), you can set the ButtonStyle property of the FXMonthView control.

Using Commands

In WPF, in order to have a loosely coupled event handling system, you'd prefer to use commands. I also advise you to use a separate static class and put your control's commands there. Here's how our commands look like:

public static class FXMonthViewCommands
{
   public static readonly RoutedCommand ChangeToNextMonth =
                    new RoutedCommand("FXMonthViewCommands.ChangeToNextMonth",
                typeof(FXMonthViewCommands));
   public static readonly RoutedCommand ChangeToNextYear =
                    new RoutedCommand("FXMonthViewCommands.ChangeToNextYear",
                typeof(FXMonthViewCommands));
}

To make these commands actually do something, you should add bindings to the commands, and put the actual code in your code-behind class. This tells the command Framework to run your code whenever a command is activated:

public FXMonthView()
{
   CommandBindings.Add(new CommandBinding
        (FXMonthViewCommands.ChangeToNextMonth, OnChangeToNextMonth));
   CommandBindings.Add(new CommandBinding
        (FXMonthViewCommands.ChangeToNextYear, OnChangeToNextYear));
}

Now the only missing part here is to decide where the command should be executed. We should add binding(s) to the controls that should execute each specific command. Here, our TodayButton should execute FXMonthViewCommands.SelectTodayDate command, so we'd add the following to our template:

<Win:FXMonthViewButton x:Name="PART_TodayDateButton"
    Command="Win:FXMonthViewCommands.SelectTodayDate" ... />

Connecting XAML to CodeBehind

We have certain details in our control that are essential. These details (namely Parts), sometimes have logic behind them which should be written in our code behind file. So, how do we connect our Parts defined in XAML to local variables in our code behind? Suppose we have our Listbox, to which we need to bind some data (Days of the month collection) in runtime. There are some guidelines to follow:

  1. Name essential parts of your control starting with PART_, this way when customizing the template/style, it is known which parts can/should not be removed from the default template:
    <Win:FXMonthViewButton x:Name="PART_TodayDateButton" ... />
  2. Specify the Parts of your control using TemplatePart Attribute, and specify the name and type of the part (The name should be the same one used in step 1). Using TemplatePart attribute is a good practice if you'd be working with other designers like Expression Blend to design the look of your controls:
    [TemplatePart(Name = "PART_PreviousMonthButton", Type = 
            typeof(RepeatButton))]
    public class FXMonthView : Control
    {
       ...
    }
  3. Override the OnApplyTemplate method and use GetTemplateChild to fetch your part. You may throw exceptions if the part is missing, or do something else, depending on the nature of your control.
    private FXMonthViewContainer container = null;
    
    public override void OnApplyTemplate()
    {
        container = GetTemplateChild("PART_MonthDays") 
            as FXMonthViewContainer;
        if(container == null)
             throw new ArgumentException();
    
        container.SelectionChanged += OnContainerSelectionChanged;
        container.LayoutUpdated += OnContainerLayoutUpdated;
    }

Setting the Culture and Flow Direction

Should you need to use other calendars, you should set the application's Culture and UICulture to your requirements and you should do this when starting the application:

public class DemoApplication : Application
{
   protected override void OnStartup(StartupEventArgs e)
   {
      //Decide which language should be used on the UI

      System.Threading.Thread.CurrentThread.CurrentCulture = 
            new System.Globalization.CultureInfo("fa-IR");
      System.Threading.Thread.CurrentThread.CurrentUICulture = 
            new System.Globalization.CultureInfo("fa-IR");

      base.OnStartup(e);
   }
}

For a control to be displayed in mirrored order, you can simply set control's FlowDirection to Right-To-Left layout (the default is Left-To-Right). When creating custom controls that'll be used in both RTL and LTR directions, almost all parts are automatically mirrored via WPF layout engine, and there's very little you may need to do. In our sample here, there was just a fix to display the shadow of the DatePicker in Bottom-Left hand of the control instead of Bottom-Right, when in RTL mode. I achieved this by using the Margin property of the DropDown's Shadow when the FlowDirection property has RTL value, using a simple Trigger:

<ControlTemplate TargetType="{x:Type Win:FXDatePicker}">
   ...
   <ControlTemplate.Triggers>
      <Trigger Property="HasDropShadow" SourceName="PART_Popup" Value="true">
         <Setter Property="Margin" TargetName="Shdw" Value="0,0,5,5"/>
         <Setter Property="Color" TargetName="Shdw" Value="#71000000"/>
      </Trigger>

      <Trigger Property="FlowDirection" Value="RightToLeft">
         <Setter Property="Margin" TargetName="Shdw" Value="5,0,0,5"/>
      </Trigger>
   </ControlTemplate.Triggers>
</ControlTemplate>
Culture Support

History

Version 1.1.0.0

  • Fixed: Issue of converting the WeekOfDay enum to integer value, which resulted in wrong weekday being shown when using the Gregorian calendar

Version 1.0.0.0

  • Providing basic FXMonthView and FXDatePicker controls
  • All the system themes (Luna variations, Royale, Aero, Classic) are implemented
  • Basic fade animation is triggered on Month/Year change. Should be improved
  • Problem when setting application's Culture to Arabic variations
  • MinDate and MaxDate is partially implemented
  • MultiSelection is only available on the UI and should be implemented completely

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here