是否有某些事件表明新的ContentTemplate已完全应用?

时间:2021-12-01 02:38:03

I have a ContentControl that I want to change it's ContentTemplate in some event. I want to add some values (text to TextBox) when control in ContentTemplate loaded. But, I has discovered that new ContentTemplate is applied (in terms of loading all controls of new template) NOT DIRECTLY after changing property ContentTemplate.

我有一个ContentControl,我想在某些情况下更改它的ContentTemplate。我想在ContentTemplate中加载控件时添加一些值(文本到TextBox)。但是,我发现在更改属性ContentTemplate后,直接应用了新的ContentTemplate(在加载新模板的所有控件方面)。

myContentControl.ContentTemplate = newContentTemplate;
// at this line controls of new template are not loaded!

I tested by added this code after that line:

我通过在该行之后添加此代码进行测试:

var cp = GetVisualChild<ContentPresenter>(myContentControl);
var txt = myContentControl.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
txt.Text = "test";

GetVisualChild

GetVisualChild

private T GetVisualChild<T>(DependencyObject parent) where T : Visual
    {
        T child = default(T);
        int numVisuals = VisualTreeHelper.GetChildrenCount(parent);
        for (int i = 0; i < numVisuals; i++)
        {
            Visual v = (Visual)VisualTreeHelper.GetChild(parent, i);
            child = v as T;
            if (child == null)
            {
                child = GetVisualChild<T>(v);
            }
            if (child != null)
            {
                break;
            }
        }
        return child;
    }

I've got an error:

我有一个错误:

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

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

Is there some event showing that new ContentTemplate is completely applied?

是否有某些事件表明新的ContentTemplate已完全应用?

EDIT 1

编辑1

@eran I tried onApplyTemplate

@eran我试过onApplyTemplate

public override void OnApplyTemplate()
{
   var cp = GetVisualChild<ContentPresenter>(Content_Option);
   var txt = Content_Option.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
   txt.Text = "test";
}

but got error:

但得到了错误:

Object reference not set to an instance of an object.

你调用的对象是空的。

EDIT 2

编辑2

this "dirty" method works just fine:

这个“脏”的方法工作得很好:

myContentControl.ContentTemplate = newContentTemplate;

System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
timer.Interval = TimeSpan.FromMilliseconds(0.000001);
timer.Tick += new EventHandler(delegate(object s, EventArgs a)
{
   timer.Stop();
   var cp = GetVisualChild<ContentPresenter>(Content_Option);
   var txt = Content_Option.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
   txt.Text = "teSt";
});
timer.Start();

can somebody help me to achieve the same result with more "clean" (profesional) way :)

有人可以用更“干净”(专业)的方式帮助我实现相同的结果:)

EDIT 3

编辑3

My Scenario is, I have a TreeView (on left side) as menu and a Grid (on right side) as display for ContentControl. TreeView has some nodes. Each node has it's own DataTemplate. Each time a TreeView node clicked, a DataTemplate is set to ContentControl and a value (ex. Path_Cover.Text) is set from database. The layout more or less like windows explorer.

我的场景是,我有一个TreeView(在左侧)作为菜单和一个Grid(在右侧)作为ContentControl的显示。 TreeView有一些节点。每个节点都有自己的DataTemplate。每次单击TreeView节点时,DataTemplate都将设置为ContentControl,并从数据库设置值(例如Path_Cover.Text)。布局或多或少像Windows资源管理器。

Well, this is all necessary code:

好吧,这是所有必要的代码:

XAML

XAML

    <UserControl.Resources>

      <DataTemplate x:Key="General">
        <StackPanel>
           <DockPanel>
               <TextBlock Text="Cover"/>
               <TextBox Name="Path_Cover"/>
           </DockPanel>
           <DockPanel>
               <TextBlock Text="Slide"/>
               <TextBox Name="Path_Slide"/>
           </DockPanel>
        </StackPanel>
      </DataTemplate>

      <DataTemplate x:Key="Appearance">
        <StackPanel>
           <DockPanel>
               <TextBlock Text="Cover"/>
               <TextBox Name="Path_Cover"/>
           </DockPanel>
           <DockPanel>
               <Button Content="Get Theme"/>
               <TextBox Name="Txt_Theme"/>
           </DockPanel>
        </StackPanel>
      </DataTemplate>

    <UserControl.REsources>

<Grid>
    <ContentControl Name="myContentControl"/>
</Grid>

Code Behind

代码背后

private void TreeMenu_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
   myContentControl.ContentTemplate =(DataTemplate)this.Resources[Tree_Menu.SelectedItem.ToString()];

   System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
   timer.Interval = TimeSpan.FromMilliseconds(0.000001);
   timer.Tick += new EventHandler(delegate(object s, EventArgs a)
   {
      timer.Stop();
      switch (Tree_Menu.SelectedItem.ToString())
      {
         case "General": 
               var cp = GetVisualChild<ContentPresenter>(Content_Option);
               var txt = Content_Option.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
               txt.Text = "test";

               txt = Content_Option.ContentTemplate.FindName("Path_Slide", cp) as TextBox;
               txt.Text = "test";
               break;

        case "Appearance": 
               var cp = GetVisualChild<ContentPresenter>(Content_Option);
               var txt = Content_Option.ContentTemplate.FindName("Txt_Theme", cp) as TextBox;
               txt.Text = "test";
               break;
      }
   });
   timer.Start();
}

I'm just need to "move" the code inside timer.tick event handler to some new event that fire after DataTemplate/ContentTemplate completely applied.

我只需要将timer.tick事件处理程序中的代码“移动”到DataTemplate / ContentTemplate完全应用后触发的一些新事件。

3 个解决方案

#1


1  

I'm aware this is quite an old question but I've been looking for an answer to this and having invented one, thought this would be a good place to share it.

我知道这是一个很老的问题,但我一直在寻找答案并发明了一个,认为这将是一个分享它的好地方。

I have simply created my own ContentPresenter class extending from the standard ContentPresenter:

我只是创建了自己的ContentPresenter类,从标准的ContentPresenter扩展而来:

public class ContentPresenter : System.Windows.Controls.ContentPresenter {

    #region RE: ContentChanged
    public static RoutedEvent ContentChangedEvent = EventManager.RegisterRoutedEvent("ContentChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ContentPresenter));
    public event RoutedEventHandler ContentChanged {
        add { AddHandler(ContentChangedEvent, value); }
        remove { RemoveHandler(ContentChangedEvent, value); }
    }
    public static void AddContentChangedHandler(UIElement el, RoutedEventHandler handler) {
        el.AddHandler(ContentChangedEvent, handler);
    }
    public static void RemoveContentChangedHandler(UIElement el, RoutedEventHandler handler) {
        el.RemoveHandler(ContentChangedEvent, handler);
    }
    #endregion

    protected override void OnVisualChildrenChanged(System.Windows.DependencyObject visualAdded, System.Windows.DependencyObject visualRemoved) {
        base.OnVisualChildrenChanged(visualAdded, visualRemoved);
        RaiseEvent(new RoutedEventArgs(ContentChangedEvent, this));
    }
}

I hope this may help those of you out there looking for a simple solution to this glaring oversight in the design of ContentPresenter.

我希望这可以帮助那些寻找ContentPresenter设计中这种明显疏忽的简单解决方案。

#2


0  

In general I do not know any events of that kind. But a typical WPF way for your scenario is this:

一般来说,我不知道任何类似的事件。但是针对您的场景的典型WPF方式是:

<ContentControl Name=myContentControl>
<ContentControl.ContentTemplate>
    <DataTemplate>
        <StackPanel>
            ...other controls here
            <TextBox Text={Binding Mode=TwoWay}/>
            ... more controls here
        </StackPanel>
    </DataTemplate>
</ContentControl.ContentTemplate>

Code behind:

代码背后:

myContentControl.Content = "Test";

Or you can bind the Content to (propperty of) a ViewModel and put the code in there.

或者您可以将内容绑定到ViewModel的(属性)并将代码放在那里。

If you want to get to a control inside the contenttemplate, you just give it a name and perform a FindName from the control to which the contenttemplate is applied. There is no need for that searching for a contentpresenter with that VisualChild stuff.

如果要访问contenttemplate中的控件,只需为其命名并从应用了contenttemplate的控件中执行FindName。没有必要使用VisualChild内容搜索contentpresenter。

I have a feeling you are mixing up controltemplates and datatemplates (contenttemplate, itemtemplate).

我有一种感觉,你混合了controltemplates和datatemplates(contenttemplate,itemtemplate)。

  1. OnApplyTemplate refers to the moment the ControlTemplate is applied, so not the ContentTemplate or any other datatemplate.
  2. OnApplyTemplate指的是应用ControlTemplate的时刻,因此不是ContentTemplate或任何其他datatemplate。
  3. ContentPresenters are a typical ingredient of ControlTemplates and not ContentTemplates.
  4. ContentPresenters是ControlTemplates的典型成分,而不是ContentTemplates。

#3


0  

I don't think there is such event in WPF framework. You can however assure that your code is run after the newly assigned content template is applied.

我认为WPF框架中没有这样的事件。但是,您可以确保在应用新分配的内容模板后运行代码。

The way to accomplish this (and a "proper" version of your "dirty" solution) is to utilize the Dispatcher associated with your ContentControl. This code will do just what you're trying to achieve:

实现此目的的方法(以及“脏”解决方案的“正确”版本)是利用与ContentControl关联的Dispatcher。这段代码将完成你想要实现的目标:

myContentControl.ContentTemplate = newContentTemplate;
myContentControl.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
{
    var cp = GetVisualChild<ContentPresenter>(myContentControl);
    var txt = myContentControl.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
    txt.Text = "test";
}));

Notice that the priority with which this code will be executed is set to DispatcherPriority.Loaded, so it will be executed with the same priority as the code you put in the FrameworkElement.Loaded event handler.

请注意,将执行此代码的优先级设置为DispatcherPriority.Loaded,因此它将以与您在FrameworkElement.Loaded事件处理程序中放入的代码相同的优先级执行。

#1


1  

I'm aware this is quite an old question but I've been looking for an answer to this and having invented one, thought this would be a good place to share it.

我知道这是一个很老的问题,但我一直在寻找答案并发明了一个,认为这将是一个分享它的好地方。

I have simply created my own ContentPresenter class extending from the standard ContentPresenter:

我只是创建了自己的ContentPresenter类,从标准的ContentPresenter扩展而来:

public class ContentPresenter : System.Windows.Controls.ContentPresenter {

    #region RE: ContentChanged
    public static RoutedEvent ContentChangedEvent = EventManager.RegisterRoutedEvent("ContentChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ContentPresenter));
    public event RoutedEventHandler ContentChanged {
        add { AddHandler(ContentChangedEvent, value); }
        remove { RemoveHandler(ContentChangedEvent, value); }
    }
    public static void AddContentChangedHandler(UIElement el, RoutedEventHandler handler) {
        el.AddHandler(ContentChangedEvent, handler);
    }
    public static void RemoveContentChangedHandler(UIElement el, RoutedEventHandler handler) {
        el.RemoveHandler(ContentChangedEvent, handler);
    }
    #endregion

    protected override void OnVisualChildrenChanged(System.Windows.DependencyObject visualAdded, System.Windows.DependencyObject visualRemoved) {
        base.OnVisualChildrenChanged(visualAdded, visualRemoved);
        RaiseEvent(new RoutedEventArgs(ContentChangedEvent, this));
    }
}

I hope this may help those of you out there looking for a simple solution to this glaring oversight in the design of ContentPresenter.

我希望这可以帮助那些寻找ContentPresenter设计中这种明显疏忽的简单解决方案。

#2


0  

In general I do not know any events of that kind. But a typical WPF way for your scenario is this:

一般来说,我不知道任何类似的事件。但是针对您的场景的典型WPF方式是:

<ContentControl Name=myContentControl>
<ContentControl.ContentTemplate>
    <DataTemplate>
        <StackPanel>
            ...other controls here
            <TextBox Text={Binding Mode=TwoWay}/>
            ... more controls here
        </StackPanel>
    </DataTemplate>
</ContentControl.ContentTemplate>

Code behind:

代码背后:

myContentControl.Content = "Test";

Or you can bind the Content to (propperty of) a ViewModel and put the code in there.

或者您可以将内容绑定到ViewModel的(属性)并将代码放在那里。

If you want to get to a control inside the contenttemplate, you just give it a name and perform a FindName from the control to which the contenttemplate is applied. There is no need for that searching for a contentpresenter with that VisualChild stuff.

如果要访问contenttemplate中的控件,只需为其命名并从应用了contenttemplate的控件中执行FindName。没有必要使用VisualChild内容搜索contentpresenter。

I have a feeling you are mixing up controltemplates and datatemplates (contenttemplate, itemtemplate).

我有一种感觉,你混合了controltemplates和datatemplates(contenttemplate,itemtemplate)。

  1. OnApplyTemplate refers to the moment the ControlTemplate is applied, so not the ContentTemplate or any other datatemplate.
  2. OnApplyTemplate指的是应用ControlTemplate的时刻,因此不是ContentTemplate或任何其他datatemplate。
  3. ContentPresenters are a typical ingredient of ControlTemplates and not ContentTemplates.
  4. ContentPresenters是ControlTemplates的典型成分,而不是ContentTemplates。

#3


0  

I don't think there is such event in WPF framework. You can however assure that your code is run after the newly assigned content template is applied.

我认为WPF框架中没有这样的事件。但是,您可以确保在应用新分配的内容模板后运行代码。

The way to accomplish this (and a "proper" version of your "dirty" solution) is to utilize the Dispatcher associated with your ContentControl. This code will do just what you're trying to achieve:

实现此目的的方法(以及“脏”解决方案的“正确”版本)是利用与ContentControl关联的Dispatcher。这段代码将完成你想要实现的目标:

myContentControl.ContentTemplate = newContentTemplate;
myContentControl.Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() =>
{
    var cp = GetVisualChild<ContentPresenter>(myContentControl);
    var txt = myContentControl.ContentTemplate.FindName("Path_Cover", cp) as TextBox;
    txt.Text = "test";
}));

Notice that the priority with which this code will be executed is set to DispatcherPriority.Loaded, so it will be executed with the same priority as the code you put in the FrameworkElement.Loaded event handler.

请注意,将执行此代码的优先级设置为DispatcherPriority.Loaded,因此它将以与您在FrameworkElement.Loaded事件处理程序中放入的代码相同的优先级执行。