I'm trying to restyle the control template of the DateTimePicker
from the Extended WPF Toolkit. Here's a picture of what it should look like:
Here's the relevant portion of the original code:
[TemplatePart( Name = PART_Calendar, Type = typeof( Calendar ) )]
[TemplatePart( Name = PART_TimeUpDown, Type = typeof( TimePicker ) )]
public class DateTimePicker : DateTimePickerBase
{
private const string PART_Calendar = "PART_Calendar";
private const string PART_TimeUpDown = "PART_TimeUpDown";
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
if( _calendar != null )
_calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged;
_calendar = GetTemplateChild( PART_Calendar ) as Calendar;
if( _calendar != null )
{
_calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
_calendar.SelectedDate = Value ?? null;
_calendar.DisplayDate = Value ?? this.ContextNow;
this.SetBlackOutDates();
}
_timePicker = GetTemplateChild( PART_TimeUpDown ) as TimePicker;
}
}
<Style TargetType="{x:Type local:DateTimePicker}">
...
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:DateTimePicker}">
...
<StackPanel>
<Calendar x:Name="PART_Calendar" BorderThickness="0" />
<local:TimePicker x:Name="PART_TimeUpDown" ... />
</StackPanel>
...
</ControlTemplate>
</Setter.Value>
<Setter>
</Style>
Now because the DateTimePicker
has a fairly good deal of logic in the code behind for adjusting properties of the the calendar part on certain events, I'd hate to reinvent the wheel. Ideally, I'd like to be able to simply restyle the control like this:
CustomStyles.xaml
<Style x:Key="MetroDateTimePicker" TargetType="{x:Type xctk:DateTimePicker}">
<Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
<Setter Property="Background" Value="{DynamicResource ControlBackgroundBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush" Value="{DynamicResource TextBoxBorderBrush}"/>
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="FontFamily" Value="{DynamicResource ContentFontFamily}"/>
<Setter Property="FontSize" Value="{DynamicResource ContentFontSize}"/>
<Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst"/>
<Setter Property="Stylus.IsFlicksEnabled" Value="False"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type xctk:DateTimePicker}">
<Grid>
<Border x:Name="Base"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
<Grid Margin="5">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<xctk:ButtonSpinner x:Name="PART_Spinner"
Grid.Column="0"
BorderThickness="0"
IsTabStop="False"
Background="Transparent"
Style="{StaticResource MetroButtonSpinner}"
AllowSpin="{TemplateBinding AllowSpin}"
ShowButtonSpinner="{TemplateBinding ShowButtonSpinner}">
<xctk:WatermarkTextBox x:Name="PART_TextBox"
BorderThickness="0"
Background="Transparent"
FontFamily="{TemplateBinding FontFamily}"
FontSize="{TemplateBinding FontSize}"
FontStretch="{TemplateBinding FontStretch}"
FontStyle="{TemplateBinding FontStyle}"
FontWeight="{TemplateBinding FontWeight}"
Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
IsReadOnly="{Binding IsReadOnly, RelativeSource={RelativeSource TemplatedParent}}"
MinWidth="20"
AcceptsReturn="False"
Padding="0"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="NoWrap"
Text="{Binding Text, RelativeSource={RelativeSource TemplatedParent}}"
TabIndex="{TemplateBinding TabIndex}"
Watermark="{TemplateBinding Watermark}"
WatermarkTemplate="{TemplateBinding WatermarkTemplate}" />
</xctk:ButtonSpinner>
<ToggleButton x:Name="_calendarToggleButton"
Background="{TemplateBinding Background}"
Grid.Column="1"
IsChecked="{Binding IsOpen, RelativeSource={RelativeSource TemplatedParent}}"
Style="{DynamicResource ChromelessButtonStyle}"
Foreground="{TemplateBinding Foreground}"
IsTabStop="False">
<Path Fill="{TemplateBinding Foreground}"
Data="..."
Stretch="Uniform">
<Path.Width>
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="FontSize"
Converter="{x:Static shared:FontSizeOffsetConverter.Instance}">
<Binding.ConverterParameter>
<sys:Double>4</sys:Double>
</Binding.ConverterParameter>
</Binding>
</Path.Width>
<Path.Height>
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="FontSize"
Converter="{x:Static shared:FontSizeOffsetConverter.Instance}">
<Binding.ConverterParameter>
<sys:Double>4</sys:Double>
</Binding.ConverterParameter>
</Binding>
</Path.Height>
</Path>
</ToggleButton>
</Grid>
<Popup x:Name="PART_Popup"
AllowsTransparency="True"
IsOpen="{Binding IsChecked, ElementName=_calendarToggleButton}"
PopupAnimation="{DynamicResource {x:Static SystemParameters.ComboBoxPopupAnimationKey}}"
StaysOpen="False">
<Border Padding="3"
Background="{DynamicResource WhiteBrush}"
BorderBrush="{DynamicResource ComboBoxPopupBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Effect="{DynamicResource DropShadowBrush}">
<StackPanel>
<Calendar x:Name="Part_Calendar"
BorderThickness="0"
MinWidth="115"
DisplayDateStart="{Binding Minimum, RelativeSource={RelativeSource TemplatedParent}}"
DisplayDateEnd="{Binding Maximum, RelativeSource={RelativeSource TemplatedParent}}"
IsTodayHighlighted="False"/>
<xctk:TimePicker x:Name="PART_TimeUpDown"
Style="{StaticResource MetroTimePicker}"
Background="{DynamicResource {x:Static SystemColors.WindowBrushKey}}"
Foreground="{DynamicResource {x:Static SystemColors.WindowTextBrushKey}}"
Format="{TemplateBinding TimeFormat}"
FormatString="{TemplateBinding TimeFormatString}"
Value="{Binding Value, RelativeSource={RelativeSource TemplatedParent}}"
Minimum="{Binding Minimum, RelativeSource={RelativeSource TemplatedParent}}"
Maximum="{Binding Maximum, RelativeSource={RelativeSource TemplatedParent}}"
ClipValueToMinMax="{Binding ClipValueToMinMax, RelativeSource={RelativeSource TemplatedParent}}"
IsUndoEnabled="{Binding IsUndoEnabled, RelativeSource={RelativeSource TemplatedParent}}"
AllowSpin="{TemplateBinding TimePickerAllowSpin}"
ShowButtonSpinner="{TemplateBinding TimePickerShowButtonSpinner}"
Watermark="{TemplateBinding TimeWatermark}"
WatermarkTemplate="{TemplateBinding TimeWatermarkTemplate}"
Visibility="{TemplateBinding TimePickerVisibility}"
Margin="3 0 3 3"/>
</StackPanel>
</Border>
</Popup>
</Grid>
<ControlTemplate.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsReadOnly, RelativeSource={RelativeSource Self}}" Value="False" />
<Condition Binding="{Binding AllowTextInput, RelativeSource={RelativeSource Self}}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsReadOnly" Value="True" TargetName="PART_TextBox" />
</MultiDataTrigger>
<DataTrigger Binding="{Binding IsReadOnly, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="IsReadOnly" Value="True" TargetName="PART_TextBox" />
</DataTrigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Note: this isn't the only thing I've changed; there are actually two more styles I've omitted (MetroTimePicker
and MetroButtonSpinner
). Everything looks good for the most part, it doesn't behave right. Whenever I set a break point in my code (after ApplyTemplate
is called), I can see the private _calendar
field is null. Directly calling myPicker.GetTemplateChild("PART_Calendar")
also returns null (this is possible in the Watch or Immediate window).
It seems that when I applied a custom style, it was no longer able to pick up the named elements in the template. I must be missing something, because I thought I could apply almost any control template as long as all the named parts where there (and had the appropriate type). So my question is, how do I apply a custom template to a WPF control and ensure that any logic associated with its named parts continues to work as expected?
Alright, I'm an idiot. I don't know how long I've looked at this, double- and triple-checked every name, but somehow I never saw this stupid little typo:
<Calendar x:Name="Part_Calendar" … />
Should have been:
<Calendar x:Name="PART_Calendar" … />