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

3

9

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.

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

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?

EDIT 1

@eran I tried 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

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

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.

Well, this is all necessary code:

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.

Fletcher answered 21/11, 2012 at 13:45 Comment(2)
OnLoad or OnLoadData is called after OnApplayTemplate check msdnRefractor
@eran Onload event only fires once. I want event that fires everytime I change the ContentTemplate.Fletcher
G
5

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:

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.

Geronimo answered 18/6, 2013 at 20:13 Comment(0)
G
2

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.

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:

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.

Glaciate answered 18/8, 2016 at 12:42 Comment(0)
S
0

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

<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.

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.

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

  1. OnApplyTemplate refers to the moment the ControlTemplate is applied, so not the ContentTemplate or any other datatemplate.
  2. ContentPresenters are a typical ingredient of ControlTemplates and not ContentTemplates.
Synchromesh answered 22/11, 2012 at 7:58 Comment(4)
I'm sorry, but I'm very new to wpf. There is so much that I need to learn first (including the mvvm concept). Could you just "follow" my code above and give me the straight answer (code) based on my flow please :P I'm just need to "move" the code inside timer.tick event handler to some new event.Fletcher
...and yes, sometimes I'm confused about DataTemplate, ContentTemplate, ItemTemplate and ContentControl. :PFletcher
I have a feeling that you want to achieve something simple, but you think in complicated solutions. There is really no need to go through the visual tree for the content presenter. So you don't need the timer. Keep it simple: use two contentcontrols with the contents you defined in the datatemplates, and switch visibility on selected. Start from there and try to make a nicer solution.Synchromesh
Yes, It can be achieved as simple as change the grid visibility on selected without using all the DataTemplates and ContentControls. But, just like I said, I'm still learning, so sometimes I want to achieve something with unusual way :P. This is one of it.Fletcher

© 2022 - 2024 — McMap. All rights reserved.