【问题标题】:How to access from code-behind an instance of a Control defined in a ControlTemplate in a Setter in a Style in an UserControl?如何从代码隐藏访问在 UserControl 的样式中的 Setter 中的 ControlTemplate 中定义的控件实例?
【发布时间】:2019-04-09 23:06:07
【问题描述】:

以前我直接设置了 UserControl 的模板,而不是通过样式,并且一切正常:可以使用 this.Template.LoadContent() 或通过 this.Template.FindName("MyControlName 访问内容的根目录", this),两个调用都在 OnApplyTemplate 中完成,在 base.OnApplyTemplate() 调用之后。现在我需要一个样式,因为我使用两个 DataTrigger 来显示一个 Control 类型或另一个在 Binding 值的函数中。

通过使用下面的 XAML 进行调试,我看到在 base.OnApplyTemplate 调用 this.Template.LoadContent() 后返回一个 Border,其 Child 设置为空 ContentPresenter。我希望得到 wpf:TimeSpanPicker 元素。

我已阅读this 的答案,但由于上面介绍的调试结果,它对我没有帮助。与this answer 相同。

以前,我的 UserControl 直接在里面(在它的 XAML 文件中):

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

现在我有了这个:

<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>

代码隐藏包括:

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;
}

ClockToType 值转换器只是将我的 Clock 类的实例之一转换为它们的类型名称。

更新

现在它部分工作,因为答案,但我需要在 UserControl 的 CurrentValue 依赖属性更改时设置 TimeSpanPicker 的 TimeSpan 依赖属性,并且当时间跨度选择器尚未更改时,可以更改 CurrentValue 依赖属性已加载。推迟此设置的最佳方法是什么?在设置之前调用 ApplyTemplate 似乎不起作用,因为我保留对 TimeSpanPicker 的引用的类变量为空:

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;
}

【问题讨论】:

  • 您可以尝试使用TemplatePart 属性将您的控件分成多个部分
  • TimeSpanValueChanged 不是路由事件吗?如果是,您可以使用样式和事件设置器或在控制级别处理它。

标签: c# wpf user-controls controltemplate


【解决方案1】:

您不能使用OnApplyTemplate(),因为在绑定解决且转换器返回“TimerData”值之前,没有可用的TimeSpanPicker 元素。

您可以做的是在 XAML 中连接事件处理程序:

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

...然后在您的代码隐藏中处理它;

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

编辑:如果您想在CurrentValue 属性更改时对TimeSpanPicker 执行某些操作,您可以使用VisualTreeHelper 类在可视化树中找到它:

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;

【讨论】:

  • 我收到此错误:The event 'Loaded' cannot be specified on a Target tag in a Style. Use an EventSetter instead.
  • 如果您将模板定义为资源并使用&lt;Setter Property="Template" Value="{StaticResource x}"&gt; 以样式引用它会怎样?
  • 我认为它有效,但我需要更多帮助。我更新了我的问题。谢谢。
  • 如果你想在CurrentValue属性改变的时候做点什么,那么你应该为这个属性注册一个回调。
  • @silviubogan:查看我的编辑。如果它存在,您应该能够在可视化树中找到它。
猜你喜欢
  • 2017-06-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-29
  • 2011-12-28
  • 2013-11-03
相关资源
最近更新 更多