How to create a WPF DateRangePicker?
Tags: csharp, wpf, usercontrol, dotnet
Published: 2023-03-16 15:30:00

I needed a way to let a user select a date range for a WPF DataGrid filter. After some research I was not able to find a control that was capable of what I wanted, so I decided to create my own WPF User Control that allows either selecting a single date or a date range.

The finished WPF User Control can be found in my GitHub profile under [XamlDateRangePicker](https://github.com/daniel-leinweber/XamlDateRangePicker).

The following article will describe the creation process and how to use the control.

## Planning
First I needed to plan what parts the user control would need to provide the functionality I was looking for. So I did a quick paper prototype.

![User Control Paper Prototype](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gclgb3nhue3p1kc2ld3a.jpg)

### Parts
1. TextBox
I wanted to show the selected date (range) similar to a DateTimePicker, so I figured I would need a Textbox.

2. Image Button
A button to open the calendar should be combined with the TextBox. I figured a ToggleButton would be the best choice for this functionality, because of its `IsChecked` state.

3. Popup
I wanted the calendar to slide down from the TextBox when the user clicked the ToggleButton, so I decided to add a Popup.

4. Calendar
The actual calendar control that is nested in the Popup.  

## Creating a WPF User Control
A typical WPF user control consists of two files.
- a **.xaml** file
- and a **.xaml.cs** file

The controls UI will be created in the **.xaml** file and it's logic in the **.xaml.cs** file.

### Creating the UI
First I added a WPF User Control to my Project to create the two needed files. Then I started to edit the `DateRangePicker.xaml` file and added the needed controls.

```xml
<UserControl x:Class="UserControls.DateRangePicker"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:UserControls"
             mc:Ignorable="d" 
             x:Name="this"
             d:DesignHeight="30" d:DesignWidth="160">

    <StackPanel>
        <Border
            BorderThickness="1"
            BorderBrush="{Binding ElementName=DateRangePicker_TextBox, Path=BorderBrush}">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="30" />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition Width="30" />
                </Grid.ColumnDefinitions>
                <TextBox
                    x:Name="DateRangePicker_TextBox"
                    Grid.Column="0"
                    Margin="0"
                    MinHeight="30"
                    BorderThickness="0"
                    d:Text="14.03.2023 - 20.03.2023"
                    Text="{Binding ElementName=this, Path=DisplayValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Delay=300}"
                    KeyUp="Handle_Escape_Button" />
                <ToggleButton
                    x:Name="DateRangePicker_ArrowButton"
                    Grid.Column="1"
                    Height="{Binding ElementName=DateRangePicker_TextBox, Path=Height}"
                    Focusable="False"
                    ClickMode="Press"
                    IsChecked="{Binding ElementName=this, Path=IsDropDownOpen, Mode=TwoWay}"
                    MinWidth="30"
                    Background="{Binding ElementName=DateRangePicker_TextBox, Path=Background}"
                    BorderBrush="{Binding ElementName=DateRangePicker_TextBox, Path=Background}"
                    BorderThickness="0"
                    HorizontalAlignment="Right"
                    VerticalAlignment="Stretch">
                    <Path
                        x:Name="DateRangePicker_Arrow"
                        Data="M 0 0 L 4 4 L 8 0 Z">
                        <Path.Fill>
                            <SolidColorBrush Color="#FF444444"/>
                        </Path.Fill>
                    </Path>
                </ToggleButton>
            </Grid>
        </Border>
        <Popup
            x:Name="DateRangePicker_Popup"
            Placement="Bottom"
            IsOpen="{Binding ElementName=this, Path=IsDropDownOpen}"
            AllowsTransparency="True"
            Focusable="False"
            PopupAnimation="Slide">

            <Viewbox
                x:Name="DateRangePicker_DropDown"
                SnapsToDevicePixels="True"
                Stretch="Fill">
                <Calendar
                    x:Name="DateRangePicker_Calendar"
                    SelectionMode="SingleRange"
                    SelectedDatesChanged="DateRangePicker_Calendar_SelectedDatesChanged"/>
            </Viewbox>
        </Popup>
    </StackPanel>
</UserControl>
```

To make sure the controls are binding to the *code behind* (.xaml.cs) I gave my user control the `x:Name="this"`. So I could use it for example in the `Text` binding of the TextBox.

```xml
Text="{Binding ElementName=this, Path=DisplayValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, Delay=300}"
```

This will search for a `DisplayValue` property in my `DateRangePicker.xaml.cs` file.

### Creating the controls logic
Then I started editing the `.xaml.cs` file to add the user controls logic. Here I defined EventHandler and Properties that are used in the XAML of the control, as well as a `DependencyProperty` that should be accessible from the outside to be able to receive the selected date (range).

#### Defining a Dependency Property
A dependency property can be bound from the outside of the control. You will need to register a property as a DependencyProperty like in the following code snippet.

```csharp
public static readonly DependencyProperty SelectedDateRangeProperty
    = DependencyProperty.Register(
        nameof(SelectedDateRange),
        typeof(ObservableCollection<DateTime>),
        typeof(DateRangePicker),
        new FrameworkPropertyMetadata(default(ObservableCollection<DateTime>), FrameworkPropertyMetadataOptions.None));

public ObservableCollection<DateTime> SelectedDateRange
{
    get => (ObservableCollection<DateTime>)GetValue(SelectedDateRangeProperty);
    set => SetValue(SelectedDateRangeProperty, value);
}
```

#### Updating / Interacting with the UI
In order to update or interact with the UI from your code behind, you need to make sure that you implement the `INotifyPropertyChanged` interface. So that you can raise a notification when you change any property that is used in your UI.

```csharp
public event PropertyChangedEventHandler PropertyChanged;

public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "", Action onChanged = null)
{
    var output = false;

    if (EqualityComparer<T>.Default.Equals(backingStore, value) == false)
    {
        backingStore = value;
        onChanged?.Invoke();
        RaisePropertyChanged(propertyName);

        output = true;
    }

    return output;
}
```

Then you can use the `SetProperty` method to raise the property change which will update the UI.

Here an example of the `SelectedDates` property that is updated from the Calendar controls `SelectedDatesChanged` event.

```csharp
public ObservableCollection<DateTime> SelectedDates
{
    get => _selectedDate;
    set => SetProperty(ref _selectedDate, value, onChanged: () =>
    {
        IsDropDownOpen = false;
        if (value.Any())
        {
            if (value.Count == 1)
            {
                DisplayValue = value.First().ToString("dd.MM.yyyy");
                SelectedDateRange = new ObservableCollection<DateTime> { value.First() };
            }
            else
            {
                DisplayValue = $"{value.First():dd.MM.yyyy} - {value.Last():dd.MM.yyyy}";
                SelectedDateRange = new ObservableCollection<DateTime> { value.First(), value.Last() };
            }
        }
        else
        {
            SelectedDateRange = new ObservableCollection<DateTime>();
        }
    });
}
private ObservableCollection<DateTime> _selectedDate = new ObservableCollection<DateTime>();
```

## Using the control
In order to use the control you need to follow the next steps.

1. Add the project or the user control to your solution/project.
2. Add the namespace for the user control to your XAML

```xml
xmlns:userControls="clr-namespace:UserControls;assembly=DateRangePicker"
```

3. Use the control in your XAML

```xml
<userControls:DateRangePicker 
    SelectedDateRange="{Binding SelectedDates, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
```

You can bind the `SelectedDateRange` Property to your ViewModel. This property holds a `ObservableCollection<DateTime>` of either one or two dates. Either the single selected DateTime or the first and the last date of the selected date range.

![Screenshot of the DateRangePicker](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ngn5j4akzfiliobqusxn.png)