0

Previously I had the template of the UserControl directly set, not through a style, and everything worked nicely: the root of the content could be accessed using this.Template.LoadContent() or through this.Template.FindName("MyControlName", this), both calls being done in OnApplyTemplate, after the base.OnApplyTemplate() call. Now I need a style because I use two DataTriggers to display a type of Control or another in function of a Binding value.

By debugging with the XAML below, I see that after the base.OnApplyTemplate call this.Template.LoadContent() returns a Border with its Child set to an empty ContentPresenter. I wish to get the wpf:TimeSpanPicker element.

I have read this answer and it does not help me because of the result of debugging presented above. The same with this answer.

Before, my UserControl had this directly inside (in its XAML file):

<UserControl.Template>
    <ControlTemplate>
        <wpf:TimeSpanPicker
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            HorizontalContentAlignment="Stretch"
            VerticalContentAlignment="Stretch"
            Name="MyTimeSpanPicker"
            Margin="0,0,7,0"/>
    </ControlTemplate>
</UserControl.Template>

Now I have this:

<UserControl.Style>
    <Style TargetType="UserControl">
        <Style.Triggers>
            <DataTrigger Binding="{Binding Mode=OneWay, Converter={StaticResource ClockToType}}"
                                        Value="TimerData">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="UserControl">
                            <wpf:TimeSpanPicker
                                HorizontalAlignment="Stretch"
                                VerticalAlignment="Stretch"
                                HorizontalContentAlignment="Stretch"
                                VerticalContentAlignment="Stretch"
                                Name="MyTimeSpanPicker"
                                Margin="0,0,7,0"/>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </DataTrigger>
            <DataTrigger Binding="{Binding Mode=OneWay, Converter={StaticResource ClockToType}}"
                                        Value="AlarmData">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="UserControl">
                            <Button>Not yet implemented</Button>
                        </ControlTemplate>
                    </Setter.Value>
                </Setter>
            </DataTrigger>
        </Style.Triggers>
    </Style>
</UserControl.Style>

The code-behind includes:

internal wpf_timespanpicker.TimeSpanPicker GetMyTimeSpanPicker()
{
    return (wpf_timespanpicker.TimeSpanPicker)Template.LoadContent();
}

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();

    GetMyTimeSpanPicker().TimeSpanValueChanged += MyTimeSpanPicker_TimeSpanValueChanged;
}

private void MyTimeSpanPicker_TimeSpanValueChanged(object sender, EventArgs e)
{
    CurrentValue = GetMyTimeSpanPicker().TimeSpan;
}

The ClockToType value converter simply transforms one of my Clock classes' instances to their type name.

Update

Now it partially works because of the answer but I need to set the TimeSpan dependency property of the TimeSpanPicker when the CurrentValue dependency property of the UserControl is changed, and the CurrentValue dependency property can be changed when the time span picker is not yet Loaded. What is the best way to postpone this setting? Placing an ApplyTemplate call before the setting does not seem to work because the class variable in which I keep a reference to the TimeSpanPicker is null:

private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var o = d as ClockValueScreen;
    o.ApplyTemplate();
    o.MyTimeSpanPicker.TimeSpan = (TimeSpan)e.NewValue; // but here o.MyTimeSpanPicker is null
}

public override void OnApplyTemplate()
{
    base.OnApplyTemplate(); // execution reaches this point from the ApplyTemplate call above
}

private void MyTimeSpanPicker_Loaded(object sender, RoutedEventArgs e)
{
    MyTimeSpanPicker = (wpf_timespanpicker.TimeSpanPicker)sender;
    MyTimeSpanPicker.TimeSpanValueChanged += MyTimeSpanPicker_TimeSpanValueChanged;
}
silviubogan
  • 3,343
  • 3
  • 31
  • 57

1 Answers1

1

You can't use OnApplyTemplate() because there is no TimeSpanPicker element available until the binding has been resolved and the converter has returned a value of "TimerData".

What you could do instead is to hook up the event handler in the XAML:

<ControlTemplate TargetType="UserControl">
    <wpf:TimeSpanPicker
            Loaded="OnLoaded"
            HorizontalAlignment="Stretch"
            VerticalAlignment="Stretch"
            HorizontalContentAlignment="Stretch"
            VerticalContentAlignment="Stretch"
            Name="MyTimeSpanPicker"
            Margin="0,0,7,0"/>
</ControlTemplate>

...and then handle it in your code-behind;

private void OnLoaded(object sender, RoutedEventArgs e)
{
     wpf_timespanpicker.TimeSpanPicker timeSpanPicker = ( wpf_timespanpicker.TimeSpanPicker)sender;
}

Edit: If you want to do something with the TimeSpanPicker when the CurrentValue property changes, you could use the VisualTreeHelper class to find it in the visual tree:

private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    var o = (ClockValueScreen)d;
    var timeSpanPicker = FindVisualChild<wpf_timespanpicker.TimeSpanPicker>(o);
    //...
}


private static T FindVisualChild<T>(Visual visual) where T : Visual
{
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
    {
        Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
        if (child != null)
        {
            T correctlyTyped = child as T;
            if (correctlyTyped != null)
            {
                return correctlyTyped;
            }

            T descendent = FindVisualChild<T>(child);
            if (descendent != null)
            {
                return descendent;
            }
        }
    }
    return null;
mm8
  • 163,881
  • 10
  • 57
  • 88
  • I get this error: `The event 'Loaded' cannot be specified on a Target tag in a Style. Use an EventSetter instead.` – silviubogan Apr 09 '19 at 13:56
  • What if you define your template as resource and reference it in the style using ``? – mm8 Apr 09 '19 at 13:58
  • I think it works but I need more help. I updated my question. Thank you. – silviubogan Apr 09 '19 at 14:56
  • If you want to do something when the `CurrentValue` property changes, then you should register a callback for this property. – mm8 Apr 09 '19 at 14:59
  • @silviubogan: See my edit. You should be able to find it in the visual tree if it's there. – mm8 Apr 09 '19 at 15:06
  • It is not in the visual tree. – silviubogan Apr 09 '19 at 15:42
  • @silviubogan: So where is it then? Obviously you won't ever be able to get a reference to an element that doesn't yet exist. That's indeed impossible. – mm8 Apr 10 '19 at 14:39