在 WPF 中,如何在模板中找到通过触发器切换的元素?

发布于 2024-11-01 11:24:00 字数 1854 浏览 0 评论 0原文

我有一个 UserControl (不是一个看起来不好看的自定义控件),它根据某些自定义状态属性在各种 ContentTemplate 中交换,所有内容模板都定义为关联的 XAML 文件中的资源。在后面的代码中,我需要找到换入的 ContentTemplates 中的元素之一。

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

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

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

有什么想法吗?

作为参考,这是我拥有的控件的一个非常精简的示例。在此示例中,如果 IsEditing 为 true,我想查找名为“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>

Aaaaaaand GO!

中号

I have a UserControl (not a lookless custom control) which, depending on some custom state properties, swaps in various ContentTemplates, all defined as resources in the associated XAML file. In the code-behind, I need to find one of the elements in the swapped-in ContentTemplates.

Now in a lookless control (i.e. a custom control), you simply override OnApplyTemplate then use FindName, but that override doesn't fire when the ContentTemplate gets switched by a trigger (...at least not for a UserControl. I haven't tested that functionality with a custom control.)

Now I've tried wiring up the Loaded event to the control in the swapped-in template, which does fire in the code-behind, then I simply store 'sender' in a class-level variable. However, when I try to clear that value by subscribing to the Unloaded event, that doesn't fire either because the tempalte gets swapped out, thus unwiring that event before it has a chance to be called and the control unloads from the screen silently, but I still have that hung reference in the code-behind.

To simulate the OnApplyTemplate functionality, I'm considering subscribing to the ContentTemplateChanged notification and just using VisualTreeHelper to look for the control I want, but I'm wondering if there's a better way, hence this post.

Any ideas?

For reference, here's a very-stripped-down example of the control I have. In this example, if IsEditing is true, I want to find the textbox named 'FindMe'. If IsEditing is false which means the ContentTemplate isn't swapped in, I want to get '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>

Aaaaaaand GO!

M

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

醉南桥 2024-11-08 11:24:00

不幸的是,没有更好的方法。您可以覆盖 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);
}

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

像这样的东西:

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

Unfortunately, there isn't a better way. You can override the OnContentTemplateChanged, instead of hooking up to the event.

You would need to use the DataTemplate.FindName method to get the actual element. The link has an example of how that method is used.

You would need to delay the call to FindName if using OnContentTemplateChanged though, as it is not applied to the underlying ContentPresenter immediately. Something like:

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

Alternatively, you may be able to attach a handler to the LayoutUpdated event of the UserControl, but this may fire more often than you want. This would also handle the cases of implicit DataTemplates though.

Something like this:

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";
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文