Zamboni
Zamboni

Reputation: 8043

Cannot get attached behavior property value when control is unloaded

I am using a ContentControl to change the controls that appear in my UI depending on the object type that a user selects via a DataTemplateSelector.

When the control gets unloaded, based on replacing the content in the ContentControl below, I can no longer get access to the attached behavior's properties in the Unloaded event handler of the attach behavior.

For example if a user clicks on a Drill unit and then clicks on a Fuel Truck unit in the UI, then I cannot get access to the attach properties in the control. Its like the when the control is unloaded its too late to read the attached properties when the DateTemplateSelector fires.

If the control gets unloaded because I close the parent window or for some other reason like hinding the parent's document tab then I have no problems reading the attached behaviors property values.

This the code fragment that is failing:

The code is trying to find attached behavior property: "behaviours:PersistUiBehavior.PersistanceChildCode" from below and I am expecting the "result" to equal 17 or 18 depending when controls is active.

public static void GetPersistenceRequiredControls(DependencyProperty property, DependencyObject root, int code, List<object> sources)
{
  if ((property != null) && (root != null))
  {
    // property = behaviours:PersistUiBehavior.PersistanceChildCode does not exist here when unloaded
    var result = (int) root.GetValue(property);
    if (result == code)
    {
      sources.Add(root);
    }
  }
 }

Here is a XAML fragment that shows the various parts:

<UserControl x:Class="FleetControl.Editors.Views.PropertyEditor">
 <UserControl.Resources>
  <ResourceDictionary>

  <templates:PropertyEditorTemplateSelector x:Key="PropertyEditorTemplateSelector">

    <templates:PropertyEditorTemplateSelector.DrillUnitEditorTemplate>
      <DataTemplate>
        <telerik:RadPropertyGrid 
          x:Name="DrillPropertyGrid"
          behaviours:PersistUiBehavior.FileName="Test1.txt"
          behaviours:PersistUiBehavior.PersistanceParentCode="5"
          behaviours:PersistUiBehavior.PersistanceChildCode="17"
          behaviours:PersistUiBehavior.Name="DrillPropertyGrid">
          <telerik:RadPropertyGrid.PropertyDefinitions>
            <telerik:PropertyDefinition DisplayName="Equipment Ident" Binding="{Binding DisplayName, Mode=OneWay}" GroupName="Description" IsReadOnly="True" />
            <telerik:PropertyDefinition DisplayName="Equipment Description" Binding="{Binding Description, Mode=OneWay}" GroupName="Description" IsReadOnly="True"/>
            <telerik:PropertyDefinition DisplayName="Equipment Type Ident" Binding="{Binding EquipmentType.Ident, Mode=OneWay}" GroupName="Description" IsReadOnly="True"/>
          </telerik:RadPropertyGrid.PropertyDefinitions>
        </telerik:RadPropertyGrid>
      </DataTemplate>
    </templates:PropertyEditorTemplateSelector.DrillUnitEditorTemplate>
    <templates:PropertyEditorTemplateSelector.FuelTruckUnitEditorTemplate>
      <DataTemplate>
        <telerik:RadPropertyGrid 
          x:Name="FuelTruckUnitPropertyGrid"
          behaviours:PersistUiBehavior.FileName="Test2.txt"
          behaviours:PersistUiBehavior.PersistanceParentCode="6"
          behaviours:PersistUiBehavior.PersistanceChildCode="18"
          behaviours:PersistUiBehavior.Name="FuelTruckUnitPropertyGrid">
          <telerik:RadPropertyGrid.PropertyDefinitions>
            <telerik:PropertyDefinition DisplayName="Location" Binding="{Binding Location.LocationName, Mode=OneWay}" GroupName="Realtime" IsReadOnly="True"/>
            <telerik:PropertyDefinition DisplayName="Location Code" Binding="{Binding Location.Code, Mode=OneWay}" GroupName="Realtime" IsReadOnly="True"/>
            <telerik:PropertyDefinition DisplayName="Location Description" Binding="{Binding Location.Description, Mode=OneWay}" GroupName="Realtime" IsReadOnly="True"/>
          </telerik:RadPropertyGrid.PropertyDefinitions>
        </telerik:RadPropertyGrid>
      </DataTemplate>
    </templates:PropertyEditorTemplateSelector.FuelTruckUnitEditorTemplate>
  </templates:PropertyEditorTemplateSelector>

 </ResourceDictionary>
</UserControl.Resources>

 <Grid>
  <Grid.RowDefinitions>
    <RowDefinition Height="*" />
  </Grid.RowDefinitions>
  <Grid.ColumnDefinitions>
    <ColumnDefinition Width="*" />
  </Grid.ColumnDefinitions>

  <ContentControl 
    ContentTemplateSelector="{StaticResource PropertyEditorTemplateSelector}"
    Content="{Binding Path=SelectedPropertyEditor, Mode=OneWay}" />
 </Grid>
</UserControl>

Upvotes: 2

Views: 636

Answers (1)

Peter Duniho
Peter Duniho

Reputation: 70691

Yes, you are correct. Attached properties (all properties, actually) are cleared when the template of a ContentPresenter is changed. This is part of the operations that are performed by the System.Windows.StyleHelper.DoTemplateInvalidations() method, called in this case by the ContentPresenter.OnTemplateChanged() method. This eventually leads to System.Windows.StyleHelper.InvalidatePropertiesOnTemplateNode() being called, which is where the actual work is done.

For example, here's the call stack I see in my debugger:

WindowsBase.dll!System.Windows.DependencyObject.InvalidateProperty(System.Windows.DependencyProperty dp, bool preserveCurrentValue) Unknown
PresentationFramework.dll!System.Windows.StyleHelper.InvalidatePropertiesOnTemplateNode(System.Windows.DependencyObject container, MS.Internal.FrameworkObject child, int childIndex, ref MS.Utility.FrugalStructList<System.Windows.ChildRecord> childRecordFromChildIndex, bool isDetach, System.Windows.FrameworkElementFactory templateRoot)    Unknown
PresentationFramework.dll!System.Windows.StyleHelper.ClearTemplateChain(System.Collections.Specialized.HybridDictionary[] instanceData, System.Windows.FrameworkElement feContainer, System.Windows.FrameworkContentElement fceContainer, System.Collections.Generic.List<System.Windows.DependencyObject> templateChain, System.Windows.FrameworkTemplate oldFrameworkTemplate)    Unknown
PresentationFramework.dll!System.Windows.StyleHelper.ClearGeneratedSubTree(System.Collections.Specialized.HybridDictionary[] instanceData, System.Windows.FrameworkElement feContainer, System.Windows.FrameworkContentElement fceContainer, System.Windows.FrameworkTemplate oldFrameworkTemplate) Unknown
PresentationFramework.dll!System.Windows.StyleHelper.DoTemplateInvalidations(System.Windows.FrameworkElement feContainer, System.Windows.FrameworkTemplate oldFrameworkTemplate)    Unknown
PresentationFramework.dll!System.Windows.Controls.ContentPresenter.OnTemplateChanged(System.Windows.DependencyObject d, System.Windows.DependencyPropertyChangedEventArgs e)    Unknown

This all happens before the Unloaded event is called. For better or worse, this is entirely by design.

Without a good Minimal, Complete, and Verifiable example that shows clearly what your code does, along with a precise explanation of what your eventual goal is for that code to do instead, I can't really say what the best alternative approach would be.

At least a couple of obvious work-arounds come to mind, both involving performing at some earlier point in time the work you were trying to do in the Unloaded event handler:

  • Do the work when the model data reference changes. I.e. handle changes to the SelectedPropertyEditor property.
  • Do the work when the attached property itself is invalidated. E.g. add a PropertyChangedCallback delegate to your attached property metadata and do the work there (i.e. when you see the attached property being changed to null from some previous value).

I'll note that in the little bit of code you posted, it appears you are trying to hang on to the DependencyObject that was declared in the template itself (e.g. the telerik:RadPropertyGrid object). IMHO this is likely to cause other problems elsewhere. I admit, I don't know exactly what problems, but it seems wrong to me to try to reach into the templated subgraph and resurrect it after WPF has already unloaded it. So even if you address the property-invalidation issue, you may run into more issues later, when you try to reuse that object.


If you need more specific help than that, please post a new question in which you've provided a good code example, along with precise details as to what specifically you want to happen when the template is unloaded, and how you expect to be able to use the templated object later once it's been unloaded.

Upvotes: 2

Related Questions