Есть ли какое-то событие, показывающее, что новый ContentTemplate полностью применен?

У меня есть ContentControl, который я хочу изменить в некотором случае его ContentTemplate. Я хочу добавить некоторые значения (текст в TextBox) при загрузке элемента управления в ContentTemplate. Но я обнаружил, что новый ContentTemplate применяется (с точки зрения загрузки всех элементов управления нового шаблона) НЕ ПРЯМО после изменения свойства ContentTemplate.

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

Я протестировал добавив этот код после этой строки:

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

У меня есть ошибка:

Эта операция действительна только для элементов, к которым применен этот шаблон.

Есть ли какое-то событие, показывающее, что новый ContentTemplate полностью применен?

РЕДАКТИРОВАТЬ 1

@eran я пробовал на ApplyTemplate

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

но получил ошибку:

В экземпляре объекта не задана ссылка на объект.

РЕДАКТИРОВАТЬ 2

этот "грязный" метод работает просто отлично:

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

Может ли кто-нибудь помочь мне достичь того же результата более "чистым" (профессиональным) способом:)

РЕДАКТИРОВАТЬ 3

Мой сценарий, у меня есть TreeView (слева) в качестве меню и Grid (справа) в качестве отображения для ContentControl. TreeView имеет несколько узлов. Каждый узел имеет свой собственный DataTemplate. При каждом щелчке узла TreeView для DataTemplate устанавливается ContentControl, а из базы данных устанавливается значение (например, Path_Cover.Text). Компоновка более или менее похожа на Windows Explorer.

Ну, это весь необходимый код:

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>

Код позади

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

Мне просто нужно "переместить" код внутри обработчика событий timer.tick в какое-то новое событие, которое запускается после полного применения DataTemplate/ContentTemplate.

3 ответа

Я знаю, что это довольно старый вопрос, но я искал ответ на него и, придумав один, подумал, что это будет хорошим местом, чтобы поделиться им.

Я просто создал свой собственный класс 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));
    }
}

Я надеюсь, что это может помочь тем из вас, кто ищет простое решение для этого вопиющего упущения в дизайне ContentPresenter.

Я не думаю, что есть такое событие в рамках WPF. Однако вы можете убедиться, что ваш код запускается после применения нового назначенного шаблона содержимого.

Способ достижения этого (и "правильная" версия вашего "грязного" решения) заключается в использовании Dispatcher связано с вашим ContentControl, Этот код будет делать то, что вы пытаетесь достичь:

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

Обратите внимание, что приоритет, с которым будет выполняться этот код DispatcherPriority.Loaded поэтому он будет выполняться с тем же приоритетом, что и код, который вы вставили в FrameworkElement.Loaded обработчик события.

Вообще я не знаю никаких событий такого рода. Но типичный способ WPF для вашего сценария таков:

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

Код позади:

myContentControl.Content = "Test";

Или вы можете связать Контент с (моделью) ViewModel и поместить туда код.

Если вы хотите получить доступ к элементу управления внутри шаблона содержимого, просто дайте ему имя и выполните FindName из элемента управления, к которому применяется шаблон содержимого. Нет необходимости в том, чтобы искать контент-презентера с этим материалом VisualChild.

У меня такое чувство, что вы путаете контрольные шаблоны и шаблоны данных (contenttemplate, itemtemplate).

  1. OnApplyTemplate относится к моменту применения ControlTemplate, а не к ContentTemplate или любой другой таблице данных.
  2. ContentPresenters являются типичным компонентом ControlTemplates, а не ContentTemplates.
Другие вопросы по тегам