Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugging: Why a data-bound section breaks off when DataContext is reapplied?

Update 2010 06 12
Distilled the essence out into a smaller sample - solution available here http://db.tt/v6r45a4

It now seems to be a problem related to the tab control : When the data context is reapplied (click the button), it seems that everything works as expected. The bindings refresh but then all bindings on the active tab are broken.

You can verify this by selecting any tab and clicking the button, you'd see the controls on that tab go kaput. Added a bunch of logging statements which is how my friend arrived at this synopsis and his fix [ Set DataContext="{Binding}" on the tab control as well ]

But we're still not sure why this behaves the way it does...

TabControl Data Context now set to ReproTabItemBug.MainViewModel
TabPage [LeftTabPage] Data Context now set to ReproTabItemBug.LeftViewModel
System.Windows.Data Error: 40 : BindingExpression path error: 'MiddleProp' property not found on 'object' ''MainViewModel' (HashCode=50608417)'. BindingExpression:Path=MiddleProp; DataItem='MainViewModel' (HashCode=50608417); target element is 'TextBox' (Name='MiddleTabTextbox'); target property is 'Text' (type 'String')
TabPage [MiddleTabPage] Data Context now set to ReproTabItemBug.MiddleViewModel
Middle tab textbox text changed to 634272638824920423
TabPage [RightTabPage] Data Context now set to ReproTabItemBug.RightViewModel
Middle tab textbox text changed to 

Previous Post (Disclaimer: Long post ahead... get your popcorn. I've spent the good part of a day on this..)

My ViewModel is composed of three POCO subViewModels. Each subVieModel has properties that are bound in the 3 sections mentioned above. Each subViewModel is exposed as a standard .net get-only property (No INotifyPropertyChanged)

My View has 3 sections. Each section has a DataContext setter something like this..

...
<TabItem  x:Name="_tabPageForVM2"  DataContext="{Binding PropReturningSubVM2}">
  <!-- followed by UI Items that are data-bound to props inside this sub-viewmodel -->
</TabItem>
...

So to summarize

<MainView> <!--DataContext set programmmatically to an Instance Of MainViewModel) -->
   <Control DataContext="{Binding PropReturningSubVM1}" >.. Section1 .. </Control>
   <Control DataContext="{Binding PropReturningSubVM2}" >.. Section2 .. </Control>
   <Control DataContext="{Binding PropReturningSubVM3}" >.. Section3 .. </Control>
</MainView>

Now here's the puzzling bit. On normal startup, I create an instance of the MainViewModel (which has the child view models passed into its ctor). The properties in a watch window confirm this.

    Trace.WriteLine("Before setting datacontext");
    mainView.DataContext = null;
    mainView.DataContext = mainViewModel;
    //mainView.Refresh();
    Trace.WriteLine("After setting datacontext");

Everything works perfectly. Now due to reasons beyond my control, there is a scenario where the UI is dismissed but the view still resides in memory. So to clear it out when it is shown the next time, I create new instances of my viewmodel and reapply the datacontext (by calling the same initialization routine as before) However when the DataContext=mainViewModel set is executed, I see a bunch of binding errors in the output window. What is interesting is that only the bindings inside one tab page (a sub view model) are broken. The other 2 sub view models function correctly - no binding errors.

System.Windows.Data Error: 40 : BindingExpression path error: 'RphGaugeMaxScale' property not found on 'object' ''MainViewModel' (HashCode=38546056)'. BindingExpression:Path=RphGaugeMaxScale; DataItem='MainViewModel' (HashCode=38546056); target element is 'Gauge' (Name='GaugeControl'); target property is 'MaxValue' (type 'Double')
System.Windows.Data Error: 40 : BindingExpression path error: 'RphGaugeMaxScale' property not found on 'object' ''MainViewModel' (HashCode=38546056)'. BindingExpression:Path=RphGaugeMaxScale; DataItem='MainViewModel' (HashCode=38546056); target element is 'Gauge' (Name='GaugeControl'); target property is 'MajorTickCount' (type 'Int32')
...

The properties exist on SubViewModel2, but instead the lookup is using the MainViewModel.

Next I added a Refresh Method on MainView (called after the DataContext is reapplied) that does this

    _tabPageForVM2.DataContext = null;
    _tabPageForVM2.DataContext = mainVM.PropReturningSubVM2;

and this fixes the problem.

What puzzles me is

  • what is special about subVM2 ? If it works the first time DataContext is assigned, why does it break the second time around ?
  • I set the header of the tabPage to {Binding} to check what it is bound to and it showns "FullTypeNameOfSubVM2", which indicates the DataContext property of the tab page is being set. So why are the bindings broken?
like image 684
Gishu Avatar asked Jun 16 '26 14:06

Gishu


1 Answers

You can download Snoop or other tool that shows you visual tree and see there that content of TabItem is not actually the visual child of this TabItem, but the visual child of TabControl.

alt text

So TabItem is the logical child of TabItem's content and TabControl is the visual child of TabItem's content. DataContext should be inherited from logical parent, but it seems to me that it's inherited randomly from either TabItem or TabControl.

The best solution I can suggest is to move bindings to content like so:

<TabItem x:Name="LeftTabPage" Header="LeftModel">
    <StackPanel Orientation="Horizontal" DataContext="{Binding Left}">
        <my:Gauge x:Name="gauge" Height="200" Width="200" Value="{Binding LeftProp}"/>
        <Viewbox Height="200" Width="200" >
            <my:Gauge x:Name="scaledGauge" Value="{Binding LeftProp}"/>
        </Viewbox>
    </StackPanel>
</TabItem>

<TabItem x:Name="MiddleTabPage" Header="{Binding}">
    <TextBox x:Name="MiddleTabTextbox" DataContext="{Binding Middle}" Text="{Binding MiddleProp}" />
</TabItem>

<TabItem x:Name="RightTabPage" Header="RightModel">
    <TextBox DataContext="{Binding Right}" Text="{Binding RightProp}"/>
</TabItem>

I think I get it. I'll try to explain.

If the second tab is active(for example), MiddleTabTextbox is logical child of MiddleTabPage and it is visual child of MyTabControl (it has 2 different parents with 2 different DataContexts). First tab is not active and it has only logical child - StackPanel. When you click the button DataContext changes. And it's ok with StackPanel - it has one parent. But what MiddleTabTextbox should do? Shoult it take DataContext from visual or logical parent? It seems logical to get context from logical parent :) I expeted this behaviour, but MiddleTabTextbox gets it from visual child i.e. MyTabControl. So instead getting MiddleViewModel you get MainViewModel with bunch of binding errors. I don't know why WPF inherit DataContext from visual parent not logical one.

like image 166
rooks Avatar answered Jun 19 '26 04:06

rooks



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!