Pour répondre à des besoins de sélection de plages horaires similaires au composant DateTimePicker d'Element UI, une solution personnalisée sous WPF s'avère nécessaire. Le DatePicker natif, composé d'un DatePickerTextBox, d'un Button et d'un Calendar généré dynamiquement, ne permet pas une personnalisation directe via son template.
Approche technique
Un UserControl DateTimePicker est implémenté avec quatre propriétés de dépendance :
- HoverStart/HoverEnd : contrôlent les dates de début/fin dans le calendrier
- DateTimeRangeStart/DateTimeRangeEnd : gèrent les valeurs sélectionnées
Structure XAML
<Grid>
<Border>
<StackPanel Orientation="Horizontal">
<Custom:HintTextBox x:Name="StartDateField"
GotFocus="OnDateFieldFocused">
<Custom:HintTextBox.Text>
<Binding Path="DateTimeRangeStart"
StringFormat="yyyy-MM-dd HH:mm">
<Binding.ValidationRules>
<validators:DateRangeRule
MaxValue="{Binding Text, ElementName=EndDateField}"/>
</Binding.ValidationRules>
</Binding>
</Custom:HintTextBox.Text>
</Custom:HintTextBox>
<TextBlock Text="~"/>
<Custom:HintTextBox x:Name="EndDateField"
GotFocus="OnDateFieldFocused">
<!-- Validation similaire -->
</Custom:HintTextBox>
<Custom:IconButton Click="ToggleCalendar"/>
</StackPanel>
</Border>
<Popup x:Name="CalendarPanel">
<Grid>
<StackPanel Orientation="Horizontal">
<Calendar x:Name="FirstCalendar"
SelectionChanged="UpdateDateSelection"/>
<Calendar x:Name="SecondCalendar"
DisplayDate="{Binding DisplayDate, ElementName=FirstCalendar}" />
</StackPanel>
<StackPanel Grid.Row="1">
<TextBlock Text="Heure début:"/>
<ComboBox x:Name="HourStart" ItemsSource="{Binding Hours}"/>
<ComboBox x:Name="MinuteStart" ItemsSource="{Binding Minutes}"/>
<TextBlock Text="Heure fin:"/>
<ComboBox x:Name="HourEnd"/>
<ComboBox x:Name="MinuteEnd"/>
</StackPanel>
<StackPanel Grid.Row="2" Orientation="Horizontal">
<Button Content="Effacer" Click="ResetSelection"/>
<Button Content="Valider" Click="ConfirmSelection"/>
</StackPanel>
</Grid>
</Popup>
</Grid>
Personnalisation du calendrier
Le style des jours utilise un MultiBinding pour gérer l'apparenec des plages sélectionnées :
<Style TargetType="CalendarDayButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Grid>
<Border x:Name="RangeHighlight"
Visibility="{Binding Converter={StaticDateRangeConverter}}">
<Border.CornerRadius>
<MultiBinding Converter="{StaticCornerRadiusConverter}"/>
</Border.CornerRadius>
</Border>
<ContentPresenter />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Convertisseur de plages
public class RangeConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object p, CultureInfo c)
{
if (values[0] is DateTime currentDay)
{
var start = (DateTime)values[1];
var end = (DateTime)values[2];
if (currentDay == start) return new CornerRadius(10, 0, 0, 10);
if (currentDay == end) return new CornerRadius(0, 10, 10, 0);
}
return new CornerRadius(0);
}
}
L'événemant SelectedDatesChanged met à jour les propriétés HoverStart et HoverEnd pour refléter la sélection.