使用现有部件重新设计控件模板

Restyle control template with existing parts

我正在尝试重新设置 Extended WPF ToolkitDateTimePicker 的控件模板的样式。这是它应该的样子的图片:

这是原始代码的相关部分:

DateTimePicker.cs

[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;
    }
}

Generic.xaml

<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>

现在因为 DateTimePicker 在代码背后有相当多的逻辑来调整某些事件的日历部分的属性,我不想重新发明轮子。理想情况下,我希望能够像这样简单地重新设置控件的样式:

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>

注意:这不是我唯一更改的内容;实际上我还省略了两种样式(MetroTimePickerMetroButtonSpinner)。在大多数情况下,一切看起来都很好,但表现不佳。每当我在代码中设置断点时(在调用 ApplyTemplate 之后),我都可以看到私有 _calendar 字段为空。直接调用 myPicker.GetTemplateChild("PART_Calendar") 也 returns null(这在 Watch 或 Immediate window 中是可能的)。

似乎当我应用自定义样式时,它不再能够拾取模板中的命名元素。我一定遗漏了一些东西,因为我认为我可以应用几乎任何控件模板,只要所有命名部分都在那里(并且具有适当的类型)。所以我的问题是,如何将自定义模板应用于 WPF 控件并确保与其命名部分关联的任何逻辑继续按预期工作?

好吧,我是个白痴。我不知道我看了多久,对每个名字都进行了双重和三次检查,但不知何故我从来没有看到这个愚蠢的小错别字:

<Calendar x:Name="Part_Calendar" … />

应该是:

<Calendar x:Name="<b><em>PART</em></b>_Calendar" … />