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

时间:2011-04-15 16:22:19

标签: wpf invalidoperationexception contenttemplateselector

好的......这让我很难过。我在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 childItem FindVisualChild<childItem>(DependencyObject obj)
    where childItem : DependencyObject
{
    for(int i = 0 ; i < VisualTreeHelper.GetChildrenCount(obj) ; i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
        if(child != null && child is childItem)
            return (childItem)child;
        else
        {
            childItem childOfChild = FindVisualChild<childItem>(child);
            if(childOfChild != null)
                return childOfChild;
        }
    }
    return null;
}

中号

3 个答案:

答案 0 :(得分:10)

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

this.ApplyTemplate(); 

答案 1 :(得分:4)

正如John指出的那样,OnContentTemplateChanged在实际应用于底层ContentPresenter之前就被触发了。因此,在应用FindName之前,您需要延迟对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);
}

或者,您可以将处理程序附加到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";
}

答案 2 :(得分:0)

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

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