8

我有一个 ContentControl,我想在某些情况下更改它的 ContentTemplate。当 ContentTemplate 中的控件加载时,我想添加一些值(文本到 TextBox)。但是,我发现在更改属性 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";

获取VisualChild

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 我试过 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";
}

但出现错误:

你调用的对象是空的。

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

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

4

3 回答 3

5

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

我只是创建了我自己的从标准 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 设计中这种明显疏忽的人。

于 2013-06-18T20:13:40.577 回答
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";

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

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

我有一种感觉,您正在混淆 controltemplates 和 datatemplates(contenttemplate、itemtemplate)。

  1. OnApplyTemplate 是指应用 ControlTemplate 的那一刻,而不是 ContentTemplate 或任何其他数据模板。
  2. ContentPresenters 是 ControlTemplates 而不是 ContentTemplates 的典型成分。
于 2012-11-22T07:58:34.757 回答
0

我认为 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事件处理程序中的代码相同的优先级执行。

于 2016-08-18T12:42:18.553 回答