wpfxamldata-bindingattachedbehaviors

Binding ElementName return an instance from a previously instancied DataTemplate


I have the following DataTemplate

<DataTemplate x:Key="ArchiveModeContentTemplate">
    <Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
                                Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}"
                                Command="{Binding ElementName=factory,Path=BuildPopup}">
                    <i:Interaction.Behaviors>
                        <pop:PopupFactory x:Name="factory" Factory="{Binding ConfirmArchivingFactory}" />
                    </i:Interaction.Behaviors>
                </Button>
    </DataTemplate>

PopupFactory has a Command BuildPopup. this Command is given to the button with a binding with ElementName.

The first time this dataTemplate is displayed, it work fine. The button get the command. But if this dataTemplate is unloaded then displayed again, the binding give to the button the command of the previous instance of PopupFactory and not the newly created instance.

I pass in the constructor of PopupFactory and it is attached to the new button. So it is not a problem of PopupFactory being shared between templates.

Why this is happening? is it a bug with a the xaml cache?

Edit

I have an even stranger bug now.
I changed the syntax to the following to have the binding elementName after the name declaration in the Xaml. Now the command is working correctly but the the second button which is using a binding RelativeSource to find a command named GoBack don't work anymore. I used snoop to check the binding and it complain that it can't find the command BuildPopup. WPF is getting crazy!

<Button Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}" Grid.Row="1" Grid.Column="0" Foreground="{x:Static ui:UbiBrushes.UbiDarkBlue}"
                                    Content="{StaticResource ValidateIcon48}" ui:StyleProperties.Label="{DynamicResource Archive}">
  <i:Interaction.Behaviors>
  <pop:PopupFactory x:Name="Archivefactory" Factory="{Binding ConfirmArchivingFactory}" IsSingleInstance="False" />
  </i:Interaction.Behaviors>
    <Button.Command>
        <Binding ElementName="Archivefactory" Path="BuildPopup" />
    </Button.Command>
</Button>

<Button Grid.Row="1" Grid.Column="1"
                                Style="{x:Static ui:ButtonStyles.DrawingButtonLabel}"
                                Content="{StaticResource CrossIcon48}"
                                Foreground="Green"
                                ui:StyleProperties.Label="{DynamicResource Cancel}"
                                Command="{Binding Path=GoBack, RelativeSource={RelativeSource AncestorType={x:Type ui:DrillDown}}}" />

Edit

Here the code of PopupFactory

public class PopupFactory : Behavior<UIElement>
    {
        public ICommand BuildPopup { get; private set; }

        private bool _canExecute;

        private IDisposable _canexecuteSubscription = null;

        public IObservable<bool> CanExecuteSource
        {
            get { return (IObservable<bool>)GetValue(CanExecuteSourceProperty); }
            set { SetValue(CanExecuteSourceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for CanExecute.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty CanExecuteSourceProperty =
            DependencyProperty.Register("CanExecute", typeof(IObservable<bool>), typeof(PopupFactory), new PropertyMetadata(null));

        private static void OnCanExecuteSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
        {
            var factory = obj as PopupFactory;
            factory._canexecuteSubscription?.Dispose();
            if (arg.NewValue != null)
            {
                factory._canexecuteSubscription = ((IObservable<bool>)arg.NewValue)
                    .ObserveOnDispatcher()
                    .Subscribe(factory.UpdateCanExecute);
            }
        }

        private void UpdateCanExecute(bool value)
        {
            _canExecute = value;
            ((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
        }

        public IFactory Factory
        {
            get { return (IFactory)GetValue(FactoryProperty); }
            set { SetValue(FactoryProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Factory.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty FactoryProperty =
            DependencyProperty.Register("Factory", typeof(IFactory), typeof(PopupFactory), new PropertyMetadata(null, OnFactoryChanged));

        private static void OnFactoryChanged(DependencyObject obj, DependencyPropertyChangedEventArgs arg)
        {
            var factory = obj as PopupFactory;
            ((RelayCommand<object>)factory.BuildPopup).RaiseCanExecuteChanged();
        }

        public UIElement PlacementTarget
        {
            get { return (UIElement)GetValue(PlacementTargetProperty); }
            set { SetValue(PlacementTargetProperty, value); }
        }

        // Using a DependencyProperty as the backing store for PlacementTarget.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PlacementTargetProperty =
            DependencyProperty.Register("PlacementTarget", typeof(UIElement), typeof(PopupFactory), new PropertyMetadata(null));

        public PlacementMode Placement
        {
            get { return (PlacementMode)GetValue(PlacementProperty); }
            set { SetValue(PlacementProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Placement.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty PlacementProperty =
            DependencyProperty.Register("Placement", typeof(PlacementMode), typeof(PopupFactory), new PropertyMetadata(PlacementMode.Center));

        public bool IsSingleInstance
        {
            get { return (bool)GetValue(IsSingleInstanceProperty); }
            set { SetValue(IsSingleInstanceProperty, value); }
        }

        // Using a DependencyProperty as the backing store for IsSingleInsance.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsSingleInstanceProperty =
            DependencyProperty.Register("IsSingleInstance", typeof(bool), typeof(PopupFactory), new PropertyMetadata(false));

        private bool _singleInstanceShowed = false;

        public PopupFactory()
        {
            BuildPopup = new RelayCommand<object>((f) =>
            {
                ShowPopup(f);
            }, (p) =>
            {
                return _canExecute && Factory != null && !_singleInstanceShowed;
            });
            UpdateCanExecute(true);
        }

        public IOverlayContainer ShowPopup(object parameter)
        {
            var param = new PopupParameter() { Owner = AssociatedObject };

            UIElement target = PlacementTarget != null ? PlacementTarget : AssociatedObject;

            var item = Factory.Build(parameter);
            param.Content = item.Item;
            param.Owner = AssociatedObject;
            param.RemoveCondition = item.DisposeStream;
            var container = OverlayManager.ShowPopup(param);
            var placement = new PopupRelativePlacement(container as FrameworkElement, target,
                       Placement, false);
            item.PostFactory?.Invoke();
            if (IsSingleInstance)
            {
                _singleInstanceShowed = true;
                OverlayManager.PopupOperations.Where((op) => op.Id == container.Id && op.Operationtype == OverlayOperation.OpType.PopupRemoved)
                .Once((_) =>
                {
                    _singleInstanceShowed = false;
                    ((RelayCommand<object>)BuildPopup).RaiseCanExecuteChanged();
                });
            }
            return container;
        }
    }

Solution

  • Problem solved.

    I moved the PopupFactory Behavior to a visual parent of the button. This way, the behavior is created before the button and WPF don't mess up the name resolution during the binding.