我有一个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";
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 我试过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完全应用后触发的一些新事件。
答案 0 :(得分: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设计中这种明显疏忽的简单解决方案。
答案 1 :(得分: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";
或者您可以将内容绑定到ViewModel的(属性)并将代码放在那里。
如果要访问contenttemplate中的控件,只需为其命名并从应用了contenttemplate的控件中执行FindName。 没有必要使用VisualChild的东西搜索contentpresenter。
我有一种感觉,你混合了controltemplates和datatemplates(contenttemplate,itemtemplate)。
答案 2 :(得分: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
事件处理程序中放入的代码相同的优先级执行。