daniilch
Topic Author
Posts: 11
Joined: 02 Mar 2022, 10:05

NoesisApp.Behavior gets detached when manipulating UI from code

26 Jan 2023, 14:37

Hello, recently I've stumbled on a following problem: I have an ItemsControl in my UI and I wish to sort items in it. Items are created and added/removed from ItemsControl.Items from code-behind. Some items do have attached Behaviors. When sorting items I naturally have to remove and insert them to ItemsControl.Items collection.

The problem is, when an item is removed from Items collection, an implementation of AttachableObject receives an event that item is detached from visual tree, and Behaviors attached to this item gets detached. However, when an item is inserted back into Items collection Behaviors are not reattached and simply stop working.

I've prepared a small sample. Lets create a TestView.xaml with the following content:
<UserControl
    ... namespaces skipped
    mc:Ignorable="d">
    <StackPanel Orientation="Vertical">
        <ItemsControl x:Name="itemsControl">
            <Border Width="100" Height="100" Background="Red">
                <i:Interaction.Behaviors>
                    <attach:TestBehaviour />
                </i:Interaction.Behaviors>
            </Border>
        </ItemsControl>
        <Button Click="Button_Click">Sort</Button>
    </StackPanel>
</UserControl>
TestView.xaml.cs:
public partial class TestView : UserControl
{
    public TestView()
    {
        InitializeComponent();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        var itemsControl = FindName("itemsControl") as ItemsControl;
        var item = itemsControl.Items[0];
        itemsControl.Items.RemoveAt(0);
        itemsControl.Items.Insert(0, item);
    }

    private void InitializeComponent()
    {
        NoesisUnity.LoadComponent(this);
    }

    protected override bool ConnectEvent(object source, string eventName, string handlerName)
    {
        if (eventName == nameof(Button.Click) && handlerName == nameof(Button_Click))
        {
            ((Button)source).Click += this.Button_Click;
            return true;
        }

        return base.ConnectEvent(source, eventName, handlerName);
    }
}
TestBehaviour just logs its attachment actions in to debug log:
public class TestBehaviour : Behavior<FrameworkElement>
    {
        protected override void OnAttached()
        {
            base.OnAttached();
            System.Diagnostics.Debug.WriteLine($"Attached to {AssociatedObject}");
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            System.Diagnostics.Debug.WriteLine($"Detached");
        }
    }
Clicking on a button in a TestView removes Border control with an attached behavior from Items collection and immediately adds it back, but debug log shows that after first click the behavior detaches and is never reattached back. I hope someone can suggest a generic workaround, because implementing some sort of custom tracking of behaviors attaching is going to be a pain. Thanks.
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: NoesisApp.Behavior gets detached when manipulating UI from code

30 Jan 2023, 14:22

As it happens in WPF, the behavior is only Detached when it is removed from the associated object, or when the associated object is destroyed.

If you are interested in knowing when an item is removed from the tree and added back, your behavior can hook to the Loaded and Unloaded events of the associated object, is that what you need?
 
daniilch
Topic Author
Posts: 11
Joined: 02 Mar 2022, 10:05

Re: NoesisApp.Behavior gets detached when manipulating UI from code

30 Jan 2023, 16:44

As it happens in WPF, the behavior is only Detached when it is removed from the associated object, or when the associated object is destroyed.
As you can see in my sample code, it's not exactly like that. In WPF I can temporarily remove object from visual tree and add it back, add behaviours on this object won't be detached. However, in Noesis removing an object from visual tree causes all attached behaviours to detach, because an implementation of NoesisApp.AttachableObject decides that removing object from visual tree means that it's about to be destroyed. To cite code from Unity SDK package:
public abstract class AttachableObject : Animatable, IAttachedObject
{
    // We use a binding to get notified when the associated object is disconnected from the tree
    // and is going to be destroyed, so we can properly detach from it
    private static readonly DependencyProperty AttachmentProperty = DependencyProperty.Register(
        ".Attachment", typeof(Visibility), typeof(AttachableObject),
        new PropertyMetadata(Detached, OnAttachmentChanged));

    public void Attach(DependencyObject associatedObject)
    {
        if (AssociatedObject != associatedObject)
        {
            ... skipped
            
            BindingOperations.SetBinding(this, AttachmentProperty, new Binding("Visibility")
            {
                RelativeSource = new RelativeSource
                {
                    AncestorType = typeof(UIElement)
                }
            });
            InitObject();
            OnAttached();
        }
    }

    private static void OnAttachmentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((Visibility)e.NewValue == (Visibility)(-1))
        {
            AttachableObject attachableObject = (AttachableObject)d;
            attachableObject.Detach();
        }
    }
}
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: NoesisApp.Behavior gets detached when manipulating UI from code

30 Jan 2023, 22:19

Oh, I see what you mean now... this looks like a bug in our C# implementation, because the behaviors shouldn't detach unless the object is going to be destroyed. Could you please report it in our bugtracker?
 
daniilch
Topic Author
Posts: 11
Joined: 02 Mar 2022, 10:05

Re: NoesisApp.Behavior gets detached when manipulating UI from code

31 Jan 2023, 07:34

Sure, I filed a bug: https://www.noesisengine.com/bugs/view.php?id=2498

By the way, I've found a workaround: I can get AttachableObject.AttachmentProperty with reflection and use DependencyProperty.GetMetadata to replace original property change callback with my own. I wrapped this logic in a Disposable object, so on Dispose I can find out which objects should really be detached and set original callback back. WPF does not allow to set PropertyChangeCallback after it has been registered, but I'd be grateful if you won't fix this discrepancy in Noesis :)
 
User avatar
sfernandez
Site Admin
Posts: 2983
Joined: 22 Dec 2011, 19:20

Re: NoesisApp.Behavior gets detached when manipulating UI from code

02 Feb 2023, 18:28

Thanks for the report.

As for the workaround, could you post it in the ticket? just curious how you implement it :)

Who is online

Users browsing this forum: Ahrefs [Bot], Bing [Bot], Google [Bot] and 33 guests