Actually there are several questions in this post, though all of them are about DataContext inheritance. I think you will have fun with these questions, if anyone can explain what's going on here, it will be greatly appreciated, however it's really not that easy to answer.
I created a CustomControl which derives from ContentControl by adding a Gutter property, so that the user of this control can specify two part of this control Content and Gutter. Here is my codes:
public class BizField : ContentControl { static BizField() { DefaultStyleKeyProperty.OverrideMetadata(typeof(BizField), new FrameworkPropertyMetadata(typeof(BizField))); } public object Gutter { get { return (object)GetValue(GutterProperty); } set { SetValue(GutterProperty, value); } } // Using a DependencyProperty as the backing store for Gutter. This enables animation, styling, binding, etc... public static readonly DependencyProperty GutterProperty = DependencyProperty.Register("Gutter", typeof(object), typeof(BizField), new UIPropertyMetadata(null, new PropertyChangedCallback(OnGutterChanged))); private static void OnGutterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var me = sender as BizField; me.OnGutterChanged(e); } private void OnGutterChanged(DependencyPropertyChangedEventArgs e) { } }
The template is quite simple:
<Style TargetType="{x:Type local:BizField}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:BizField}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <StackPanel> <ContentPresenter/> <ContentPresenter ContentSource="Gutter"/> </StackPanel> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style>
Now if I use it in the main window, it works as expected:
<Window x:Class="DataContexPropagate.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DataContexPropagate" Title="MainWindow" Height="350" Width="525"> <Grid> <local:BizField> <Button Content="Click one"/> <local:BizField.Gutter> <Button Content="{Binding}"/> </local:BizField.Gutter> </local:BizField> </Grid></Window>
public partial class MainWindow : Window { public MainWindow() { //Notice DataContext is set before parsing the baml this.DataContext = "Hello"; InitializeComponent(); } }
The button defined in the Gutter shows "Hello". Now I try to add the gutter as BizField's logical child.
private void OnGutterChanged(DependencyPropertyChangedEventArgs e) { this.AddLogicalChild(Gutter); }
It still works fine, but if I set the DataContext after loading xaml, it will broke.
public partial class MainWindow : Window{ public MainWindow() { InitializeComponent(); //Set datacontext after parsing baml this.DataContext = "Hello"; }}
If you run the application, the button show nothing, apparently DataContext Inheritance doesn' t work properly now. but if I change the button to a TextBlock, it will work. what's the magic with TextBlock?
<Window x:Class="DataContexPropagate.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DataContexPropagate" Title="MainWindow" Height="350" Width="525"> <Grid> <local:BizField> <Button Content="Click one"/> <local:BizField.Gutter> <TextBlock Text="{Binding}"/> </local:BizField.Gutter> </local:BizField> </Grid></Window>
To make the databinding works for the button, I have to add following codes to BizControl.
protected override System.Collections.IEnumerator LogicalChildren { get { yield return Content; yield return Gutter; } }
According to this example, you will find that "setting datacontext before or after parsing Baml does matter". But I don't why and more intersting what happened to the TextBlock?
Full source codes for BizField:
public class BizField : ContentControl { static BizField() { DefaultStyleKeyProperty.OverrideMetadata(typeof(BizField), new FrameworkPropertyMetadata(typeof(BizField))); } public object Gutter { get { return (object)GetValue(GutterProperty); } set { SetValue(GutterProperty, value); } } // Using a DependencyProperty as the backing store for Gutter. This enables animation, styling, binding, etc... public static readonly DependencyProperty GutterProperty = DependencyProperty.Register("Gutter", typeof(object), typeof(BizField), new UIPropertyMetadata(null, new PropertyChangedCallback(OnGutterChanged))); private static void OnGutterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) { var me = sender as BizField; me.OnGutterChanged(e); } private void OnGutterChanged(DependencyPropertyChangedEventArgs e) { //Comment this out, it will search for the visual parent. this.AddLogicalChild(Gutter); } //Only by adding this method, it can work properly. protected override System.Collections.IEnumerator LogicalChildren { get { yield return Content; yield return Gutter; } } }
<Window x:Class="DataContexPropagate.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DataContexPropagate" Title="MainWindow" Height="350" Width="525"> <Grid> <local:BizField> <Button Content="Click one"/> <local:BizField.Gutter> <Button Content="{Binding}"/> <!--<TextBlock Text="{Binding}"/>--> </local:BizField.Gutter> </local:BizField> </Grid></Window>
public partial class MainWindow : Window{ public MainWindow() { //Notice DataContext is set before parsing the baml //this.DataContext = "Hello"; InitializeComponent(); //Set datacontext after parsing baml this.DataContext = "Hello"; }}