【问题标题】:In WPF, how do I find an element in a template that's switched in via a trigger?在 WPF 中,如何在模板中找到通过触发器切换的元素?
【发布时间】:2011-04-15 08:50:40
【问题描述】:

我有一个 UserControl(不是一个不起眼的自定义控件),它根据一些自定义状态属性交换各种 ContentTemplates,所有内容都定义为相关 XAML 文件中的资源。在代码隐藏中,我需要在换入的 ContentTemplates 中找到其中一个元素。

现在在一个无外观的控件(即自定义控件)中,您只需覆盖 OnApplyTemplate 然后使用 FindName,但是当 ContentTemplate 被触发器切换时,该覆盖不会触发(...至少对于 UserControl 而言不是。我尚未使用自定义控件测试该功能。)

现在我已尝试将 Loaded 事件连接到已交换模板中的控件,该模板在代码隐藏中触发,然后我只需将“发送者”存储在类级变量中。但是,当我尝试通过订阅 Unloaded 事件来清除该值时,它也不会触发,因为模板被换出,因此在它有机会被调用之前取消连接该事件并且控件从屏幕上静默卸载,但我在代码隐藏中仍然有那个挂起的引用。

为了模拟 OnApplyTemplate 功能,我正在考虑订阅 ContentTemplateChanged 通知并仅使用 VisualTreeHelper 来查找我想要的控件,但我想知道是否有更好的方法,因此发布了这篇文章。

有什么想法吗?

作为参考,这是我拥有的控件的一个非常精简的示例。在这个例子中,如果 IsEditing 为真,我想找到名为“FindMe”的文本框。如果 IsEditing 为 false,这意味着 ContentTemplate 没有被换入,我想得到 'null'...

<UserControl x:Class="Crestron.Tools.ProgramDesigner.Controls.EditableTextBlock"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:Crestron.Tools.ProgramDesigner.Controls"
    x:Name="Root">

    <UserControl.Resources>

        <DataTemplate x:Key="EditModeTemplate">

            <TextBox x:Name="FindMe"
                Text="{Binding Text, ElementName=Root}" />

        </DataTemplate>

        <Style TargetType="{x:Type local:EditableTextBlock}">
            <Style.Triggers>

                <Trigger Property="IsEditing" Value="True">
                    <Setter Property="ContentTemplate" Value="{StaticResource EditModeTemplate}" />
                </Trigger>

            </Style.Triggers>
        </Style>

    </UserControl.Resources>

    <TextBlock x:Name="TextBlock"
        Text="{Binding Text, ElementName=Root}" />

</UserControl>

啊啊啊,去吧!

M

【问题讨论】:

    标签: wpf templates triggers findcontrol contenttemplateselector


    【解决方案1】:

    很遗憾,没有更好的方法。您可以覆盖 OnContentTemplateChanged,而不是挂接到事件。

    您需要使用DataTemplate.FindName 方法来获取实际元素。该链接有一个如何使用该方法的示例。

    如果使用 OnContentTemplateChanged,您需要延迟对 FindName 的调用,因为它不会立即应用于底层 ContentPresenter。比如:

    protected override void OnContentTemplateChanged(DataTemplate oldContentTemplate, DataTemplate newContentTemplate) {
        base.OnContentTemplateChanged(oldContentTemplate, newContentTemplate);
    
        this.Dispatcher.BeginInvoke((Action)(() => {
            var cp = FindVisualChild<ContentPresenter>(this);
            var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
            textBox.Text = "Found in OnContentTemplateChanged";
        }), DispatcherPriority.DataBind);
    }
    

    或者,您可以将处理程序附加到 UserControl 的 LayoutUpdated 事件,但这可能会比您想要的更频繁地触发。不过,这也可以处理隐式 DataTemplates 的情况。

    类似这样的:

    public UserControl1() {
        InitializeComponent();
        this.LayoutUpdated += new EventHandler(UserControl1_LayoutUpdated);
    }
    
    void UserControl1_LayoutUpdated(object sender, EventArgs e) {
        var cp = FindVisualChild<ContentPresenter>(this);
        var textBox = this.ContentTemplate.FindName("EditTextBox", cp) as TextBox;
        textBox.Text = "Found in UserControl1_LayoutUpdated";
    }
    

    【讨论】:

    • 啊啊!不知道 OnContentTemplateChanged 覆盖。这应该够了吧。有一件事,在链接中的代码示例中,对于一个列表框,它显示“IsSynchronizedWithCurrentItem 设置为 True 以使其正常工作”与代码行“ListBoxItem myListBoxItem = (ListBoxItem)(myListBox.ItemContainerGenerator.ContainerFromItem(myListBox.Items.CurrentItem ));'你知道他们为什么不使用'myListBox.SelectedItem'吗?那么我认为您不必使用“CurrentItem”,对吗?还是我错过了什么?
    • @MarqueIV - 是的,看起来 SelectedItem 与 Items.CurrentItem 的值相同,但您必须对其进行测试。如果您没有使用 ListBox,那么您不需要这样做。看起来您正在使用 TextBlock,对吗?它应该是一个 ContentControl,不是吗?
    • 我注意到奇怪的事情......当 IsEditing 为假时(即我还没有在备用模板中交换)ContentTemplate 返回“null”。我认为它至少会为 UserControl 选择默认的“模板”(即 USerControl 中的主要元素),但事实并非如此。所以看起来我将不得不检查 ContentTemplate 是否为空,如果不是,请使用它,但如果是,请转到常规 COntrol.FindName 方法。这只是一个猜测,但我必须测试。 (顺便说一句,这就是我推迟将其标记为已接受的原因。从技术上讲,我还没有做到。)
    • @MarqueIV - 没问题。我不认为 ContentTemplate 是默认设置的。通常,在这种情况下使用隐式 DataTemplate(即只有 TargetType,但没有键的 DataTemplate)。
    • 仍然遇到问题。我放入 OnContentTemplateChanged 覆盖并调用 'var textBox = newContentTemplate.FindName("EditTextBox", this);'并得到一个错误,您只能在应用了该模板的控件上调用它。但是在即时窗口中我做了'? newContentTemplate == this.ContentTemplate' 并且它返回 true! WTF?!!
    猜你喜欢
    • 2011-08-29
    • 2017-03-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-17
    • 2017-09-23
    • 2012-05-14
    • 1970-01-01
    相关资源
    最近更新 更多