为什么会“this.ContentTemplate.FindName”?在其自己的模板上抛出 InvalidOperationException?

发布于 2024-11-01 15:39:48 字数 1256 浏览 1 评论 0原文

好吧...这让我难住了。我已经在我的 UserControl 子类中重写了 OnContentTemplateChanged 。我正在检查为 newContentTemplate 传入的值实际上等于 this.ContentTemplate (它确实如此),但是当我调用此...

var textBox = this.ContentTemplate.FindName("EditTextBox", this);

...它会抛出以下异常...

“此操作仅对应用了此模板的元素有效。”

根据另一个相关问题中的评论者,他说你应该传递控件的内容呈现器,而不是控件本身,所以我然后尝试了这个...

var cp = FindVisualChild<ContentPresenter>(this);

var textBox = this.ContentTemplate.FindName("EditTextBox", cp);

...其中 FindVisualChild 是只是 MSDN 示例(见下文)中使用的辅助函数,用于查找关联的内容呈现器。当找到 cp 时,它也会抛出相同的错误。我被难住了!

这是供参考的辅助函数...

private TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
where TChildItem : DependencyObject {

    for(int i = 0 ; i < VisualTreeHelper.GetChildrenCount(obj) ; i++) {

        var child = VisualTreeHelper.GetChild(obj, i);

        if(child is TChildItem typedChild) {
            return typedChild;
        }
        else {
            var childOfChild = FindVisualChild<TChildItem>(child);
            if(childOfChild != null)
                return childOfChild;
        }
    }

    return null;
}

Ok... this has me stumped. I've overridden OnContentTemplateChanged in my UserControl subclass. I'm checking that the value passed in for newContentTemplate does in fact equal this.ContentTemplate (it does) yet when I call this...

var textBox = this.ContentTemplate.FindName("EditTextBox", this);

...it throws the following exception...

"This operation is valid only on elements that have this template applied."

Per a commenter in another related question, he said you're supposed to pass in the content presenter for the control, not the control itself, so I then tried this...

var cp = FindVisualChild<ContentPresenter>(this);

var textBox = this.ContentTemplate.FindName("EditTextBox", cp);

...where FindVisualChild is just a helper function used in MSDN's example (see below) to find the associated content presenter. While cp is found, it too throws the same error. I'm stumped!!

Here's the helper function for reference...

private TChildItem FindVisualChild<TChildItem>(DependencyObject obj)
where TChildItem : DependencyObject {

    for(int i = 0 ; i < VisualTreeHelper.GetChildrenCount(obj) ; i++) {

        var child = VisualTreeHelper.GetChild(obj, i);

        if(child is TChildItem typedChild) {
            return typedChild;
        }
        else {
            var childOfChild = FindVisualChild<TChildItem>(child);
            if(childOfChild != null)
                return childOfChild;
        }
    }

    return null;
}

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

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

发布评论

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

评论(3

沧桑㈠ 2024-11-08 15:39:48

在调用 FindName 方法之前显式应用模板将防止出现此错误。

this.ApplyTemplate(); 

Explicitly applying the template before calling the FindName method will prevent this error.

this.ApplyTemplate(); 
郁金香雨 2024-11-08 15:39:48

正如 John 指出的那样,OnContentTemplateChanged 在实际应用于底层 ContentPresenter 之前就被触发了。因此,您需要延迟对 FindName 的调用,直到应用它。例如:

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

As John pointed out, the OnContentTemplateChanged is being fired before it is actually applied to the underlying ContentPresenter. So you'd need to delay your call to FindName until it is applied. 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";
}
岁月苍老的讽刺 2024-11-08 15:39:48

直到该事件发生之后,ContentTemplate 才会应用于 ContentPresenter。虽然此时在控件上设置了 ContentTemplate 属性,但它尚未被推送到 ControlTemplate 内部的绑定,如 ContentPresenter 的 ContentTemplate。

您最终想用 ContentTemplate 做什么?可能有更好的总体方法来实现您的最终目标。

The ContentTemplate isn't applied to the ContentPresenter until after that event. While the ContentTemplate property is set on the control at that point, it hasn't been pushed down to bindings internal to the ControlTemplate, like the ContentPresenter's ContentTemplate.

What are you ultimately trying to do with the ContentTemplate? There might be a better overall approach to reach your end goal.

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