Skip to content

[TransitioningContentControl] Manage his LogicalChildren #12173

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Aug 15, 2023

Conversation

danielmayost
Copy link
Contributor

@danielmayost danielmayost commented Jul 13, 2023

What does the pull request do?

There are two problems with TransitioningContentControl:

  • Duplicated logical children, this is because ContentPresenter manages his host's LogicalChildren (in this case TCC), and in addition ContentControl also manages LogicalChildren, which causes logical children to be duplicated.
  • The first ContentPresenter not register the TCC as its Host, which causes it to not manage the LogicalChildren of the TCC.

We need to decide who will manage the logical children, CC (ContentControl) or TCC, in this case it is clear that TCC should do it, because CC is only adapted to one child, whereas TCC can have two (during animation).

The implementation was carried out by adding a BypassLogicalChildrenManagement property to the CC that allows the controls inheriting from it to manage the LogicalChildren themselves, in this case, TCC does not need to do anything, his presenters do it for him. see comment below.

The second problem is a bug in the RegisterContentPresenter implementation that returned false even if Presenter is PART_ContentPresenter, which causes the Host registration to be skipped:

private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)
{
var host = e.NewValue as IContentPresenterHost;
Host = host?.RegisterContentPresenter(this) == true ? host : null;
}

The fixes is pretty obvious.

Checklist

Breaking changes

No.

Fixed issues

Fixes #12158

@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0037671-beta. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@danielmayost danielmayost changed the title [TransitioningContentControl] Register TCC as host in first presenter and manage the LogicalChildren himself [TransitioningContentControl] Manage the LogicalChildren himself Jul 13, 2023
@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0037673-beta. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul
Copy link
Member

MrJul commented Jul 13, 2023

Nice one!

ContentPresenter automatically setting the ContentControl logical children makes sense, but it's really easy to forget about this relationship.

I think it would be nice to have debug warnings in the future when a logical child is added to a parent while it already has one. Even if there are valid use cases for this (as discussed with grokys previously in #10413), it could help catching this kind of problem sooner.

@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0037696-beta. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@X9VoiD
Copy link

X9VoiD commented Jul 14, 2023

Can confirm it works! 👍

The ContentChanged could probably be a protected virtual. I'm not familiar with Avalonia's codebase tho and I might just be missing information so feel free to disregard what I said.

@danielmayost
Copy link
Contributor Author

The ContentChanged could probably be a protected virtual.

Then when we override it in the inheriting class without implementing anything, it won't be so clear why. I thought that way it would be clearer.

@danielmayost danielmayost changed the title [TransitioningContentControl] Manage the LogicalChildren himself [TransitioningContentControl] Manage his LogicalChildren Jul 18, 2023
@MrJul
Copy link
Member

MrJul commented Jul 18, 2023

Then when we override it in the inheriting class without implementing anything, it won't be so clear why. I thought that way it would be clearer.

A simple comment could work. Alternatively, ContentControl can be changed to also use OnPropertyChanged, with TransitioningContentControl doing the same without calling the base for ContentProperty (with a comment, still). No new public members this way.

@danielmayost
Copy link
Contributor Author

A simple comment could work.

IMO it doesn't look aesthetic, but you decide.

Alternatively, ContentControl can be changed to also use OnPropertyChanged, with TransitioningContentControl doing the same without calling the base for ContentProperty (with a comment, still).

Oh, this is a bad idea IMO, for two reasons:

  • Avalonia's analyzer doesn't like it (actually throws a compilation error), to overriding OnPropertyChanged and not calling the base class.
  • This is a problematic practice, you can't know who is in the hierarchy chain that OnPropertyChanged must be called, if you don't want to call the one in front of you it doesn't mean you don't want to call the one that's twice behind it.

@MrJul
Copy link
Member

MrJul commented Jul 19, 2023

I think there's a misunderstanding here, I was definitely not advocating on never calling the base.OnPropertyChanged, that would indeed break everything! Sorry if I wasn't clear.

Not calling the base was only for ContentProperty in TCC, and after checking the code it's already the very case today:

protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
	if (change.Property == ContentProperty)
	{
		UpdateContent(true);
		return;
	}

	base.OnPropertyChanged(change);
}

(ContentControl would need to use OnPropertyChanged instead of AddClassHandler.)

@danielmayost
Copy link
Contributor Author

danielmayost commented Jul 19, 2023

Got it, so what's better?
I think the second option is better (OnPropertyChanged).

Edit: On second thought it still sounds like a bad idea to me. Consider the following case:

public class Class1
{
    protected void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
    {
        base.OnPropertyChanged(change);

        if (change.Property == ContentProperty)
        {
            // Do something that must be done
        }
}

public class Class2 : Class1
{
    protected void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
    {
        base.OnPropertyChanged(change);

        if (change.Property == ContentProperty)
        {
            // Do something that doesn't have to be done
        }
}

public class Class3 : Class2
{
    protected void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
    {
        if (change.Property == ContentProperty)
        {
            // Skip what doesn't have to be done
            return;
        }

        base.OnPropertyChanged(change);
}

The code in Class1 will not be executed, we just wanted to skip on the Class2 code.

@MrJul
Copy link
Member

MrJul commented Jul 21, 2023

Yes, that's a general problems with overrides and I agree it can be a pain when you're class 3 and need to repeat a behavior from class 1 that have been inhibited by class 2. Unfortunately C# doesn't allow calling an arbitrary ancestor's virtual method non-virtually (it's possible in IL).

In this particular case, the Content property is defined by ContentControl: TransitioningContentControl being a direct inheritor, there's no class before or between, plus both classes are in the same codebase: everything is in control. (The problem might apply to a potential inheritor of TransitioningContentControl, but I don't think that's a scenario we need to support.)

To me it's a win as it avoids defining a specific public flag that will only be used by TransitioningContentControl in practice. That said, I'll let an OG team member decides what's best.

@danielmayost
Copy link
Contributor Author

@MrJul No matter, It's done, can it be merged now?

@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0038197-beta. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@avaloniaui-team
Copy link
Contributor

You can test this PR using the following package version. 11.0.999-cibuild0038428-beta. (feed url: https://nuget-feed-all.avaloniaui.net/v3/index.json) [PRBUILDID]

@MrJul MrJul self-requested a review August 15, 2023 22:11
Copy link
Member

@MrJul MrJul left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@MrJul MrJul added this pull request to the merge queue Aug 15, 2023
Merged via the queue into AvaloniaUI:master with commit 7a66ea7 Aug 15, 2023
@danielmayost danielmayost deleted the tcc-fixes branch August 16, 2023 10:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UserControl loses its visual children when reused by TransitioningContentControl
4 participants