控件模板故事板,在同一模板中设置其他控件的值

3
我被要求围绕现有的DateTimePicker控件创建一个黑客工具。通常,日期/时间选择器具有一个精美的日历图像,然后是旁边用于显示实际日期的文本框。用户可以单击图像,弹出日历,并在选择后将日期刷新到文本框区域。
我的问题在于其他设计人员不喜欢日历图形,而是希望使用普通文本框控件,但是如果用户双击以打开弹出式日历,则获取日期并刷新它。由于在S / O上找到了其他帮助,所以我非常接近。
因此,为了描述我的控件模板,并保持简单(除非我需要根据建议进行自定义控件)。控件模板基于文本框控件。我们有一个边框,其包含用于文本框的PART_ContentHost,然后是标准日历控件的弹出窗口。
对于控件模板触发器,我将其链接到ScrollViewer(文本框输入区域),以进行MouseDoubleClick事件。如果触发,则将弹出窗口的IsOpen设置为true并公开日历。这很好用。
现在,完成它。如果用户从日历中选择一个日期,则下一个触发器会关闭弹出窗口(将IsOpen设置为false)。这也起作用。
我的问题在于,我还想在选择时获取所选日期的ToString()日期表示,并将其放入ScrollViewer.Content(x:Name =“PART_ContentHost”)。
<ControlTemplate TargetType="{x:Type TextBox}" x:Key="CTTextBox" >
   <StackPanel>
      <Border x:Name="targetBorder" 
         BorderBrush="{TemplateBinding BorderBrush}"
         SnapsToDevicePixels="true">

         <ScrollViewer x:Name="PART_ContentHost"
            Background="{TemplateBinding Background}"
            BorderBrush="{TemplateBinding BorderBrush}"
            Foreground="{TemplateBinding Foreground}" />
      </Border>
      <Popup PlacementTarget="{Binding ElementName=PART_ContentHost}" x:Name="PopCal">
         <Calendar x:Name="ActualCalendar"/>
      </Popup>
   </StackPanel>

   <ControlTemplate.Triggers>
      <EventTrigger RoutedEvent="ScrollViewer.MouseDoubleClick" SourceName="PART_ContentHost">
         <BeginStoryboard>
            <Storyboard>
               <BooleanAnimationUsingKeyFrames 
                  Storyboard.TargetName="PopCal" 
                  Storyboard.TargetProperty="(Popup.IsOpen)">
                  <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="True"/>
               </BooleanAnimationUsingKeyFrames>
            </Storyboard>
         </BeginStoryboard>
      </EventTrigger>

      <EventTrigger RoutedEvent="Calendar.SelectedDatesChanged" SourceName="ActualCalendar">
         <BeginStoryboard>
            <Storyboard>
               <BooleanAnimationUsingKeyFrames 
                  Storyboard.TargetName="PopCal" 
                  Storyboard.TargetProperty="(Popup.IsOpen)">
                  <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value="False"/>
               </BooleanAnimationUsingKeyFrames>
            </Storyboard>


            WHAT WOULD I PUT HERE to have the selected date of the popup calendar
            inserted into the content of the PART_ContentHost...
            <Storyboard>
               <BooleanAnimationUsingKeyFrames 
                  Storyboard.TargetName="PART_ContentHost" 
                  Storyboard.TargetProperty="(Content)">
                  <DiscreteBooleanKeyFrame KeyTime="00:00:00" Value=" ????? "/>
               </BooleanAnimationUsingKeyFrames>
            </Storyboard>

         </BeginStoryboard>
      </EventTrigger>
   </ControlTemplate.Triggers>
</ControlTemplate>

<Style TargetType="{x:Type TextBox}" x:Key="STextBox" >
   <!--<Setter Property="OverridesDefaultStyle" Value="True"/>-->
   <Setter Property="FontFamily" Value="Arial" />
   <Setter Property="FontSize" Value="12" />
   <Setter Property="Height" Value="20" />
   <Setter Property="Width" Value="100" />
   <Setter Property="VerticalContentAlignment" Value="Bottom" />
   <Setter Property="HorizontalContentAlignment" Value="Left" />
   <Setter Property="HorizontalAlignment" Value="Left" />
   <!-- Padding is Left, Top, Right, Bottom -->
   <Setter Property="Padding" Value="2,0,0,2" />
   <Setter Property="Margin" Value="0,0,0,0" />

   <Setter Property="Visibility" Value="Visible" />
   <Setter Property="IsEnabled" Value="True" />

   <Setter Property="CharacterCasing" Value="Upper" />
   <Setter Property="BorderThickness" Value="1" />

   <Setter Property="BorderBrush" Value="Black" />
   <Setter Property="Background" Value="White" />
   <Setter Property="Foreground" Value="Black" />

   <Setter Property="Template" Value="{StaticResource CTTextBox}" />
</Style>
1个回答

2

我相信这个问题可以用几种方式解决,但在这种情况下,我通常会使用附加行为。但是由于控件的模板已经实现,所以情况并不完全正常。无论如何,我认为附加行为适合这种情况,在你的位置上我会这样做。

附加行为是一种非常强大和方便的解决方案,完全满足MVVM模式,并且可以在Blend中使用(具有预定义接口)。附加行为是一种附加属性,它具有事件处理程序来更改此属性,并且所有逻辑都在此处理程序中实现。

在开始实现行为之前,我建议考虑一些对模板所做的更改。

我有点不明白为什么您要使用ScrollViewer控件作为PART_ContentHost,也许会有几个日期需要滚动显示。在WPF中,有两个控件需要显示内容:

  1. ContentPresenter
  2. ContentControl

这是它们的主要目标。第一个最轻,通常在模板中始终使用,但它不支持我们需要协调工作的事件,因此我选择了ContentControl。针对模板添加了一些小细节的绑定属性,设置为Popup:

AllowsTransparency="True"
VerticalOffset="4"
HorizontalOffset="-5" 

为了更好的可视化效果,现在进入行为示例。

XAML

<Window.Resources>
    <ControlTemplate x:Key="CTTextBox" TargetType="{x:Type TextBox}">
        <StackPanel AttachedBehaviors:SelectDateBehavior.IsEnabled="True"> <!-- Here is determined behaviour -->
            <Border x:Name="targetBorder" 
                    Width="{TemplateBinding Width}"
                    Height="{TemplateBinding Height}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}"
                    Background="{TemplateBinding Background}"
                    TextBlock.Foreground="{TemplateBinding Foreground}"
                    SnapsToDevicePixels="True">

                <ContentControl x:Name="ContentHost"
                                Content="{TemplateBinding Text}"
                                HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                                Margin="4,0,0,0" />
            </Border>

            <Popup x:Name="PopCal" 
                   AllowsTransparency="True"
                   VerticalOffset="4"
                   HorizontalOffset="-5"
                   PlacementTarget="{Binding ElementName=ContentHost}">

                <Calendar x:Name="ActualCalendar" />
            </Popup>
        </StackPanel>
    </ControlTemplate>

    <Style TargetType="{x:Type TextBox}" x:Key="STextBox">
        <Setter Property="FontFamily" Value="Arial" />
        <Setter Property="FontSize" Value="12" />
        <Setter Property="Height" Value="25" />
        <Setter Property="Width" Value="100" />
        <Setter Property="VerticalContentAlignment" Value="Center" />
        <Setter Property="HorizontalContentAlignment" Value="Left" />
        <Setter Property="HorizontalAlignment" Value="Center" />       
        <Setter Property="CharacterCasing" Value="Upper" />
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="BorderBrush" Value="Gray" />
        <Setter Property="Background" Value="AliceBlue" />
        <Setter Property="Foreground" Value="Black" />
        <Setter Property="Template" Value="{StaticResource CTTextBox}" />
    </Style>
</Window.Resources>

<Grid>
    <TextBox Style="{StaticResource STextBox}"
             Text="Select date" />
</Grid>

附加行为

public class SelectDateBehavior
{
    #region IsEnabled Dependency Property

    public static readonly DependencyProperty IsEnabledProperty;

    public static void SetIsEnabled(DependencyObject DepObject, bool value)
    {
        DepObject.SetValue(IsEnabledProperty, value);
    }

    public static bool GetIsEnabled(DependencyObject DepObject)
    {
        return (bool)DepObject.GetValue(IsEnabledProperty);
    }

    static SelectDateBehavior()
    {
        IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled",
                                                            typeof(bool),
                                                            typeof(SelectDateBehavior),
                                                            new UIPropertyMetadata(false, IsEnabledChanged));
    }

    #endregion

    #region IsEnabledChanged Handler

    private static void IsEnabledChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) 
    {
        Panel panel = sender as Panel;

        if (panel != null)
        {
            if (e.NewValue is bool && ((bool)e.NewValue) == true)
            {
                panel.Loaded += new RoutedEventHandler(panelLoaded);
            }
            else
            {
                panel.Loaded -= new RoutedEventHandler(panelLoaded);
            }
        }
    }

    #endregion

    #region Panel Loaded Handler

    private static void panelLoaded(object sender, RoutedEventArgs e) 
    {
        Panel panel = sender as Panel;
        Border border = panel.FindName("targetBorder") as Border;
        ContentControl contentHost = border.FindName("ContentHost") as ContentControl;
        Popup popup = panel.FindName("PopCal") as Popup;

        if (popup != null)
        {
            Calendar calendar = popup.FindName("ActualCalendar") as Calendar;                
            calendar.SelectedDatesChanged += new EventHandler<SelectionChangedEventArgs>(calendarSelectedDatesChanged);
        }

        if (contentHost != null)
        {
            contentHost.MouseDoubleClick += new MouseButtonEventHandler(contentHostMouseDoubleClick);
        }          
    }

    #endregion

    #region ContentHost MouseDoubleClick Handler

    private static void contentHostMouseDoubleClick(object sender, MouseButtonEventArgs e) 
    {
        ContentControl contentHost = sender as ContentControl;
        Border border = contentHost.Parent as Border;
        Panel panel = border.Parent as Panel;
        Popup popup = panel.FindName("PopCal") as Popup;

        if (popup != null) 
        {
            popup.IsOpen = true;
        }
    }

    #endregion

    #region Calendar SelectedDatesChanged Handler

    private static void calendarSelectedDatesChanged(object sender, SelectionChangedEventArgs e) 
    {
        Calendar calendar = sender as Calendar;
        Popup popup = calendar.Parent as Popup;
        Panel panel = popup.Parent as Panel;
        Border border = panel.FindName("targetBorder") as Border;
        ContentControl contentHost = border.FindName("ContentHost") as ContentControl;

        if (popup != null) 
        {
            contentHost.Content = calendar.SelectedDate;
            popup.IsOpen = false;
        }
    }

    #endregion
}

输出

输入图像说明

这里进行了当前日期的设置:

private static void calendarSelectedDatesChanged(object sender, SelectionChangedEventArgs e) 
{
    // Skipped a few lines of code
    if (popup != null) 
    {
        contentHost.Content = calendar.SelectedDate;
        popup.IsOpen = false;
    }
}

一些注意事项

首先,我们不得不放弃 EvenTrigger Storyboard,因为在 WPF 动画中使用最高优先级设置值,这意味着如果我们在动画中设置了IsOpen的值,从其他来源(代码等)访问是不可能的。所以我将所有触发器/事件留在行为侧面。

其次,解决方案与模板和控件的结构紧密相连。这意味着如果您必须更改模板的结构,则必须更改行为(可能不多)。

此示例可在此处找到。


注意到了几个好点子,我正在尝试其他定义类的替代方案... - DRapp
@DRapp:请问您为什么要寻找类定义的替代方案?对我来说,类的行为是独立的,它只存在于一个命名空间中。 - Anatoliy Nikolaev
1
我正在通过类来处理,因为我们的框架中还有一些其他的基础元素需要应用。我会把它放在那里,并根据您在这里的反馈进行大部分封装。谢谢。 - DRapp

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接