享受代码,享受人生

SOA is an integration solution. SOA is message oriented first.
The Key character of SOA is loosely coupled. SOA is enriched
by creating composite apps.
posts - 213, comments - 2314, trackbacks - 162, articles - 45
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

Weird behavior of DataContext Inheritance

Posted on 2010-06-13 20:16 idior 阅读(...) 评论(...) 编辑 收藏

Actually there are several questions in this post, though all of them are about DataContext inheritance. I think you will have 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";
       
    }
}