Skip to content
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

Inconsistent behavior for [Logical|Visual]Tree extensions #3487

Open
Sergio0694 opened this issue Sep 21, 2020 · 4 comments
Open

Inconsistent behavior for [Logical|Visual]Tree extensions #3487

Sergio0694 opened this issue Sep 21, 2020 · 4 comments

Comments

@Sergio0694
Copy link
Member

@Sergio0694 Sergio0694 commented Sep 21, 2020

Describe the bug

There are a number of inconsistencies in the various extensions in the LogicalTree and VisualTree extensions. Specifically, in the way input arguments are validated and used to perform the various requested operations. For instance:

public static FrameworkElement FindParentByName(this FrameworkElement element, string name)
{
if (element == null || string.IsNullOrWhiteSpace(name))
{
return null;
}

If element is null, the method will gracefully bail and return null.

public static T FindParent<T>(this FrameworkElement element)
where T : FrameworkElement
{
if (element.Parent == null)
{
return null;
}

If element is null, the method will throw a NullReferenceException on line 237.

Similarly, the former will match the name with the input element as a first step, and then navigate upwards, whereas the second one will not check the input element at all and only look for matches in the visual hierarchy. I'd argue the latter is more correct and appropriate for the name of the method, but regardless, the behavior should be consistent.

There are a number of similar issues in the two classes, plus some optimizations that could be done while we're at it (namely, to manually unroll some recursive tail calls, since the C# compiler doesn't support the .tail opcode automatically).

cc. @michael-hawker If I'm not mistaking, these would be the ones you asked me about a while back too? 🤔

Also, I think we should agree on a standard handling of arguments in order for them to be properly annotated.
Personally I would suggest:

  • Marking input arguments as non-nullable reference types, and use the implicit NRE if they're null
  • Marking the returned value as nullable, ie. FrameworkElement?

I can help with this and make a PR when we agree on how to approach these various points.
The changes would go into 7.0 anyway, so I think it would be ok for us to make breaking changes 😊

Package Version(s): Microsoft.Toolkit.Uwp.UI 6.1.1, from master

@Sergio0694 Sergio0694 added this to the 7.0 milestone Sep 21, 2020
@Sergio0694 Sergio0694 added this to To do in Bugs 7.0 via automation Sep 21, 2020
@michael-hawker
Copy link
Member

@michael-hawker michael-hawker commented Sep 21, 2020

@Sergio0694 I was referring to the Visual/Logic tree helpers before, so appreciate you taking a look. Agree we should be consistent here, I think originally I was thinking the element would never be null as an extension method, but it doesn't mean it can't just be called directly either.

Happy for you to take a stab at working your magic here to optimize and improve these methods. I think we have some unit tests??? If not, we certainly should. 😋

@michael-hawker
Copy link
Member

@michael-hawker michael-hawker commented Sep 22, 2020

Yeah, think the Gaze usages would be a prime example of scenarios where performance can matter. Not sure if those scenarios can be better optimized independently or require new methods/features to our existing set to better centralize code. Something to look at though.

@michael-hawker
Copy link
Member

@michael-hawker michael-hawker commented Sep 22, 2020

For instance with the Gaze code, if we had an alternate FindAscendant method which could take a filter type function (to look for some statement about the item in the tree), we could do something like this:

element = element.FindSelfOrAscendant<UIElement>(current => GazeTargetItem.GetOrCreate(current).IsInvokable);
invokable = GazeTargetItem.GetOrCreate(element);

vs.

invokable = null;
if (element != null)
{
    invokable = GazeTargetItem.GetOrCreate(element);

    while (element != null && !invokable.IsInvokable)
    {
        element = VisualTreeHelper.GetParent(element) as UIElement;

        if (element != null)
        {
            invokable = GazeTargetItem.GetOrCreate(element);
        }
    }
}

This could work for a variety of scenarios, for instance looking for a visible or collapsed element, etc... Kind of a broader case than some of our named element finders.

Thinking in the above scenario for the typed info, I'd see the filter being strongly typed, as you'd still only be looking for elements of the specified type and then further checking if they meet this extra criteria.

@michael-hawker
Copy link
Member

@michael-hawker michael-hawker commented Dec 1, 2020

I think we lack testing (surprisingly) for these functions as well, so a suite of test cases for these functions would be good to have as well.

@michael-hawker michael-hawker added this to To do in Technical 7.0 via automation Jan 6, 2021
@michael-hawker michael-hawker removed this from To do in Bugs 7.0 Jan 6, 2021
@msftbot msftbot bot modified the milestone: 7.0 Jan 6, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Technical 7.0
  
To do
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
2 participants
You can’t perform that action at this time.