【问题标题】:Control Template Storyboard, set value in other control within same template控件模板故事板,在同一模板内的其他控件中设置值
【发布时间】:2014-02-06 15:13:30
【问题描述】:

我被要求围绕现有的 DateTimePicker 控件创建一个 hack。通常,日期/时间选择器具有精美的日历图像,然后是它旁边的文本框,用于显示实际日期。用户可以点击图片,弹出日历显示给他们,选择后,日期会刷新到文本框区域。

我遇到的问题是其他设计师不喜欢日历图形,只想要一个纯文本框控件,但如果用户双击打开弹出日历,获取日期并刷新它。感谢在 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>

【问题讨论】:

    标签: c# wpf controltemplate event-triggers


    【解决方案1】:

    我确信问题可以通过多种方式解决,但在这些情况下,我通常会执行附加行为。但是虽然情况不太正常,因为这里实现了控件的模板。无论如何,我认为附加行为适合这种情况,在你的位置我会这样做。

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

    在开始意识到行为之前,我会考虑我对您的模板所做的一些更改。

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

    1. 内容演示者
    2. 内容控制

    这是他们的主要目标。模板中通常总是使用第一个最轻的,但它不支持我们需要协调工作的事件,所以我选择了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>
    

    Atatched Behavior

    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
    }
    

    Output

    设置当前日期在这里进行:

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

    Some notes

    让我提请注意一些功能。首先,我们必须去掉 EventTrigger Storyboard,因为在 WPF 动画中设置最高优先级的值​​​​,这意味着如果我们一旦在动画中设置值IsOpen,从其他来源(代码等)访问是不可能。所以我把所有的触发器/事件都放在了副行为上。

    其次,解决方案与模板和控件的结构密切相关。这意味着如果你必须改变模板的结构,你就必须改变和行为(可能不多)。

    这个例子在here可用。

    【讨论】:

    • 注意到几个优点,我正在尝试定义类的其他替代方法......
    • @DRapp:请问您为什么要寻找类定义的替代方案?对我来说,类行为是独立的,它只是在一个命名空间中。
    • 我正在通过类处理,因为在我们的东西框架中还有一些其他底层附加元素需要应用。我会把它放在那里,并认为我已经根据您在这里的反馈完成了大部分内容。谢谢
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-22
    • 2011-06-06
    • 1970-01-01
    • 1970-01-01
    • 2012-07-27
    • 1970-01-01
    相关资源
    最近更新 更多