2012-11-21 13 views
7

我有一个ContentControl,我想在某些事件中更改它的ContentTemplate。我想在加载ContentTemplate中的控件时添加一些值(文本到TextBox)。 但是,我发现应用新的ContentTemplate(在加载新模板的所有控件方面)不是直接更改属性ContentTemplate后。是否有一些事件显示新的ContentTemplate已完全应用?

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

我得到了一个错误:

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

我这条线测试后,通过添加该代码

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

有一些事件显示新的ContentTemplat e是完全适用的?

编辑1

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

,但得到的错误:

Object reference not set to an instance of an object.

EDIT 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资源管理器。

嗯,这是所有必要的代码:

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

我只需要 “移动” 内部定时器的代码.tick事件处理程序添加到在DataTemplate/ContentTemplate完全应用后触发的一些新事件。

+0

在OnApplayTemplate之后调用OnLoad或OnLoadData 检查msdn –

+0

@eran Onload事件只触发一次。我想要每次更改ContentTemplate时触发的事件。 – Reyn

回答

0

一般来说,我不知道任何这类事件。但是,一个典型的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"; 

或者你可以绑定内容到视图模型(的propperty),并把代码中那里。

如果您想要访问contenttemplate中的控件,只需给它一个名称并从应用了contenttemplate的控件执行FindName。 没有必要用VisualChild的东西来搜索contentpresenter。

我有一种感觉,你正在混合控制模板和数据模板(contenttemplate,itemtemplate)。

  1. OnApplyTemplate指的是应用ControlTemplate的时刻,而不是ContentTemplate或任何其他数据模板。
  2. ContentPresenters是ControlTemplates的典型组件,而不是ContentTemplates。
+0

我很抱歉,但我对wpf很陌生。我需要首先学习的东西太多了(包括mvvm概念)。你能不能“跟随”我的代码,并根据我的流程给我直接的答案(代码):P我只需要将timer.tick事件处理程序中的代码“移动”到某个新事件。 – Reyn

+0

...是的,有时我对DataTemplate,ContentTemplate,ItemTemplate和ContentControl感到困惑。 :P – Reyn

+0

我有一种感觉,你想实现一些简单的事情,但你认为在复杂的解决方案。实际上不需要通过内容呈现器的可视化树。所以你不需要计时器。保持简单:将两个内容控件与您在数据模板中定义的内容一起使用,并切换选定的可见性。从那里开始并尝试制定更好的解决方案。 – Grafix

1

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

我只是创建了自己的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的设计。

+0

谢谢,完美无缺! – BendEg

0

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

完成此操作(以及“脏”解决方案的“正确”版本)的方法是使用与您的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"; 
})); 

注意,与此代码将被执行设置为DispatcherPriority.Loaded,所以它会与相同的优先级,你把代码执行的优先级在FrameworkElement.Loaded事件处理程序中。

相关问题