Warm tip: This article is reproduced from serverfault.com, please click

UWP: Why behaviors in DataTemplate has a null AssociatedObject?

发布于 2020-12-02 07:35:19

I want to bind the CommandBar on NavigationView Header to a collection to dynamically display the AppBarButtons and I used the method given in this answer. However, when I set a behavior on a CommandBar in DataTemplate and run the program, it throw an exception showing that AssociatedObject is null. Here is the code:
xaml:

<i:Interaction.Behaviors>
    <behaviors:NavigationViewHeaderBehavior
        DefaultHeader="{x:Bind ViewModel.Selected.Content, Mode=OneWay}">
        <behaviors:NavigationViewHeaderBehavior.DefaultHeaderTemplate>
            <DataTemplate>
                <Grid>
                    <CommandBar Name="headerCommandBar">
                        <i:Interaction.Behaviors>
                            <behaviors:BindableCommandBarBehavior
                                PrimaryCommands="{Binding Path=Content.ViewModel.AppBarButtonList,ElementName=shellFrame, Mode=OneWay}" />
                        </i:Interaction.Behaviors>
                    </CommandBar>
                </Grid>
            </DataTemplate>
        </behaviors:NavigationViewHeaderBehavior.DefaultHeaderTemplate>
    </behaviors:NavigationViewHeaderBehavior>
</i:Interaction.Behaviors>

C# code in the ViewModel:

public ObservableCollection<AppBarButton> AppBarButtonList { get; set; }
    = new ObservableCollection<AppBarButton>
    {
        new AppBarButton { Icon = new SymbolIcon(Symbol.Accept), Label="Accept" },
        new AppBarButton { Icon = new SymbolIcon(Symbol.Add), Label="Add" }
    };

The code of behavior:

public class BindableCommandBarBehavior : Behavior<CommandBar>
{
    public ObservableCollection<AppBarButton> PrimaryCommands
    {
        get { return (ObservableCollection<AppBarButton>)GetValue(PrimaryCommandsProperty); }
        set { SetValue(PrimaryCommandsProperty, value); }
    }

    public static readonly DependencyProperty PrimaryCommandsProperty
        = DependencyProperty.Register(
            "PrimaryCommands",
            typeof(ObservableCollection<AppBarButton>),
            typeof(BindableCommandBarBehavior),
            new PropertyMetadata(default(ObservableCollection<AppBarButton>), UpdateCommands));

    private static void UpdateCommands(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
    {
        if (!(dependencyObject is BindableCommandBarBehavior behavior)) return;

        var oldList = dependencyPropertyChangedEventArgs.OldValue as ObservableCollection<AppBarButton>;
        if (dependencyPropertyChangedEventArgs.OldValue != null)
        {
            oldList.CollectionChanged -= behavior.PrimaryCommandsCollectionChanged;
        }

        var newList = dependencyPropertyChangedEventArgs.NewValue as ObservableCollection<AppBarButton>;
        if (dependencyPropertyChangedEventArgs.NewValue != null)
        {
            newList.CollectionChanged += behavior.PrimaryCommandsCollectionChanged;
        }
        behavior.UpdatePrimaryCommands();
    }


    private void PrimaryCommandsCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        UpdatePrimaryCommands();
    }

    /// <summary>
    /// Update the primary commands for CommandBar.
    /// </summary>
    private void UpdatePrimaryCommands()
    {
        if (PrimaryCommands != null)
        {
            AssociatedObject.PrimaryCommands.Clear();
            foreach (var command in PrimaryCommands)
            {
                AssociatedObject.PrimaryCommands.Add(command);
            }
        }
    }

    protected override void OnDetaching()
    {
        base.OnDetaching();
        if (PrimaryCommands != null)
        {
            PrimaryCommands.CollectionChanged -= PrimaryCommandsCollectionChanged;
        }
    }
}

The exception is thrown in UpdatePrimaryCommands method. And I'm wondering why it happens.

Questioner
myfix16
Viewed
0
Quercus 2020-12-02 15:42:26

Your commands are updated before AssociatedObject is actually attached. Add test for AssociatedObject != null in UpdateCommands and create method OnAttached that will call UpdateCommands again:

/// <summary>
/// Update the primary commands for CommandBar.
/// </summary>
private void UpdatePrimaryCommands()
{
    if (PrimaryCommands != null && AssociatedObject != null)
    {
        AssociatedObject.PrimaryCommands.Clear();
        foreach (var command in PrimaryCommands)
        {
            AssociatedObject.PrimaryCommands.Add(command);
        }
    }
}

protected override void OnAttached()
{
    UpdatePrimaryCommands();
}