简体   繁体   English

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

[英]Control Template Storyboard, set value in other control within same template

I've been asked to sort of create a hack around the already existing DateTimePicker control. 我被要求在现有的DateTimePicker控件周围创建一个hack。 Normally, the date/time picker has that wonderful image of a calendar, then the textbox next to it for showing an actual date. 通常,日期/时间选择器具有日历的精彩图像,然后是旁边的文本框,用于显示实际日期。 The user can click on the image and have the popup calendar presented to them, and upon selection, the date is refreshed into the textbox area. 用户可以单击图像并向其显示弹出日历,并且在选择时,日期将刷新到文本框区域中。

The problem I have is the other designers don't like the calendar graphic and want just a plain textbox control, but if the user double-clicks, to open a popup calendar, get the date and refresh it. 我遇到的问题是其他设计师不喜欢日历图形,只想要一个纯文本框控件,但如果用户双击,打开一个弹出日历,获取日期并刷新它。 I'm very close on this thanks to other help as found out here on S/O. 感谢我在S / O上发现的其他帮助,我非常接近。

So, to describe my control template, and keeping as plain (non custom control unless I need to per suggestions). 所以,要描述我的控件模板,并保持简单(非自定义控件,除非我需要根据建议)。 The control template is based on a textbox control. 控件模板基于文本框控件。 We have a border with the PART_ContentHost for the textbox, and then a popup of a standard calendar control. 我们为文本框设置了PART_ContentHost的边框,然后是标准日历控件的弹出框。

For the control template triggers, I have one linked to the ScrollViewer (textbox entry area) to it's MouseDoubleClick event. 对于控件模板触发器,我有一个链接到ScrollViewer(文本框输入区域)到它的MouseDoubleClick事件。 If triggered, sets the IsOpen of the popup to true and exposes the calendar. 如果已触发,则将弹出窗口的IsOpen设置为true并公开日历。 This works fine. 这很好用。

Now, to finish it. 现在,完成它。 If the user selects a date from the calendar, the next trigger closes the popup (IsOpen set to false). 如果用户从日历中选择日期,则下一个触发器将关闭弹出窗口(IsOpen设置为false)。 This too works. 这也有效。

My problem. 我的问题。 I also want upon selection to take the date selected and get its ToString() date representation put into the ScrollViewer.Content (x:Name="PART_ContentHost). 我还希望在选择时选择日期并将其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>

I am sure that the problem can be solved in several ways, but in these situations I usually do attached behaviour. 我确信问题可以通过多种方式解决,但在这些情况下,我通常会做出附加行为。 But while situation is not quite normal, because there is realized a template for the control. 但是情况不太正常,因为实现了控制的模板。 In any case, I think attached behaviour would suit for this case, in your place I would be so done. 在任何情况下,我认为附加行为适合这种情况,在你的位置我会这样做。

Attached behaviour is very powerful and convenient solution that fully satisfies the MVVM pattern, which can also be used in the Blend (with a pre-defined interface). 附加行为是非常强大且方便的解决方案,完全满足MVVM模式,也可以在Blend中使用(具有预定义的接口)。 Attached behaviour - is an attached property, which has an event handler to change this property and all the logic is implemented in this handler. 附加行为 - 是附加属性,它具有一个事件处理程序来更改此属性,并且所有逻辑都在此处理程序中实现。

Before starting to realize the behaviour I would consider some changes that I made to your template. 在开始意识到这种行为之前,我会考虑对模板进行的一些更改。

I'm a little not understand why you are using as PART_ContentHost ScrollViewer control, perhaps there will be several dates and will need to display them with scrolling. 我有点不明白你为什么使用PART_ContentHost ScrollViewer控件,也许会有几个日期,需要用滚动显示它们。 In WPF, there are two controls that are needed to display the contents: 在WPF中,显示内容需要两个控件:

  1. ContentPresenter ContentPresenter
  2. ContentControl ContentControl中

that is their main goal. 这是他们的主要目标。 The first lightest is usually always used in the templates, but it does not support the events, that we need to coordinate the work, so I chose ContentControl . 第一个最轻的通常总是在模板中使用,但它不支持我们需要协调工作的事件,因此我选择了ContentControl On the little things added binding properties for the template, set for Popup : 在小东西上为模板添加了绑定属性,为Popup设置:

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

For better visualization. 为了更好的可视化 Now move on to the example of behavior. 现在转到行为的例子。

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

在此输入图像描述

Setting the current date is carried here: 在此处设置当前日期:

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

Let me draw attention to some features. 让我提请注意一些功能。 First, we had to get rid of EvenTrigger Storyboard, because in WPF animation at the highest priority setting values​​, this means that if once we set the value IsOpen in animation, access from other sources (code, etc.) is not possible. 首先,我们必须摆脱EvenTrigger Storyboard,因为在优先级最高的设置值的WPF动画中,这意味着如果我们在动画中设置值IsOpen ,则无法从其他来源(代码等)进行访问。 So I had all the triggers / events leave on the side behavior. 所以我把所有的触发器/事件留在了侧面行为上。

Second, the solution is strongly tied to the structure of the template and control. 其次,解决方案与模板和控件的结构紧密相关。 This means that if you have to change the structure of the template, you have to change and behavior (probably not much). 这意味着如果你必须改变模板的结构,你必须改变和行为(可能不多)。

This example is available here . 此示例可在此处获得

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM