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). 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. In WPF, there are two controls that are needed to display the contents:
- ContentPresenter
- 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
. On the little things added binding properties for the template, set for 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. 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.