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

Lazy Load Node Context Menu #12074

Merged
merged 24 commits into from
Oct 12, 2021
Merged

Lazy Load Node Context Menu #12074

merged 24 commits into from
Oct 12, 2021

Conversation

OliverEGreen
Copy link
Contributor

Purpose

This PR improves the way in which all nodes' context menus are loaded, so that they are constructed when required by the user, and disposed immediately after closing. This lazy-loaded approach is designed to improve graph opening speed and reduce Dynamo's memory usage.

The new approach is handled by a new static class: NodeContextMenuBuilder.cs. This flexible class can be adapted in the future, should new MenuItems be required.

Any MenuItems created during a node's NodeViewCustomization process are stashed per-node, and dynamically injected into the ContextMenu at a consistent location when the menu is built.

Please Note:

Declarations

Check these if you believe they are true

  • The codebase is in a better state after this PR
  • Is documented according to the standards
  • The level of testing this PR includes is appropriate
  • User facing strings, if any, are extracted into *.resx files
  • All tests pass using the self-service CI.
  • Snapshot of UI changes, if any.

Reviewers

@saintentropy
@mjkkirschner
@QilongTang

FYIs

@Amoursol

@Amoursol
Copy link
Contributor

@OliverEGreen is it possible to include the benchmarking numbers you have in the description here? Before and After 😄

@OliverEGreen
Copy link
Contributor Author

@OliverEGreen is it possible to include the benchmarking numbers you have in the description here? Before and After 😄

image

<ContextMenu Name="MainContextMenu"
x:FieldModifier="public"
Closed="MainContextMenu_OnClosed"
Style="{StaticResource NodeContextMenuStyle}">
Copy link
Member

Choose a reason for hiding this comment

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

I have two questions here:

  1. I think including this style for menuItems in contextMenu.Resources can be optimized - can it also be moved to the sharedResource dictionary and only applied in the nodeContextMenuBuilder and applied there?

  2. Why does this style include references to the dismissed badge, is that in the context menu at certain times?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. As far as I can tell this is a dumb WPF thing:

We can style the ContextMenu from a ResourceDictionary fine, but styling the items is a little harder. ContextMenus can contain both MenuItems and Separators (we have both of these in the mockup).
Telling the ContextMenu that its ItemContainerStyle should be a style specifically targeting MenuItems won't work and this results in an exception. The Separators don't like being given a style that's not for them.

Therefore, I put both the style for Separators and MenuItems within a local resources section, so they get picked up as is appropriate and with no exceptions.

  1. Yes it is! It's there at all times.

Copy link
Contributor Author

@OliverEGreen OliverEGreen Sep 29, 2021

Choose a reason for hiding this comment

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

Am investigating whether we can use an ItemContainerStyleSelector to get past # 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've had no luck with implementing the ItemContainerStyleSelector. It seems like a sensible approach, but I've not seen it being used inside of a ResourceDictionary.

/// <summary>
/// A dictionary of MenuItems which are added during certain nodes' NodeViewCustomization process.
/// </summary>
private Dictionary<string, object> NodeViewCustomizationMenuItems { get; } = new Dictionary<string, object>();
Copy link
Member

Choose a reason for hiding this comment

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

why are the values of type object and not MenuItem?

Copy link
Member

Choose a reason for hiding this comment

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

@OliverEGreen a few other issues with this...

Do you need the order of keys / values in this dictionary to stay stable? If that is the case you need to use an ordered dictionary like:
https://docs.microsoft.com/en-us/dotnet/api/system.collections.specialized.ordereddictionary?view=netframework-4.8

also I was curious why you did not just use a hashset - it looks like MenuItem's base class ItemControl implements Equals
https://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/windows/Controls/ItemsControl.cs,3808

though - if you need order, go with the ordered dictionary, hashset does not preserve insertion order either.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"why are the values of type object and not MenuItem?"

As above, they can be both Separators and MenuItems.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'd say order stability does matter yeah, will switch out to an OrderedDictionary!

I considered HashSet, but I wasn't sure about using Equals to compare one MenuItem to another - would that work? Hence, I'm using the 'Header' values to indicate uniqueness.


private void MainContextMenu_OnClosed(object sender, RoutedEventArgs e)
{
grid.ContextMenu.Items.Clear();
Copy link
Member

@mjkkirschner mjkkirschner Sep 28, 2021

Choose a reason for hiding this comment

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

if you've already gone this far, can't we get rid of the ContextMenu as well, why instantiate one contextMenu per nodeView when we only need 1 at a time in the entire graph.

⚠️

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If it's possible to style this centrally then it should be possible. However, we're relying on a lot of locally-defined XAML styles currently.

Properties.Resources.NodeContextMenuShowLabels,
Properties.Resources.NodeContextMenuRenameNode,
Properties.Resources.ContextMenuLacing,
//Wpf.Properties.Resources.NodeInformationalStateDismissedAlerts,
Copy link
Member

Choose a reason for hiding this comment

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

??

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is intentionally commented out, to be tied together with the node info states once that work has been merged.


private static void AddInjectedNodeViewCustomizationMenuItems(Dictionary<string, object> nodeViewCustomizationMenuItems)
{
foreach (KeyValuePair<string, object> keyValuePair in nodeViewCustomizationMenuItems)
Copy link
Member

Choose a reason for hiding this comment

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

var

Copy link
Contributor Author

Choose a reason for hiding this comment

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

var doesn't let me grab the Key or Value, which is needed here.
I've swapped for:

foreach (DictionaryEntry keyValuePair in nodeViewCustomizationMenuItems) { ContextMenu.Items.Add(keyValuePair.Value); }

Copy link
Member

@mjkkirschner mjkkirschner Sep 29, 2021

Choose a reason for hiding this comment

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

var doesn't change the type, the type of item being iterated over is already inferred from the type of nodeViewCustomizationMenuItems - it's a KeyValuePair no matter what.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think it will let me access the Value property if it's 'var' though:

image

Copy link
Member

@mjkkirschner mjkkirschner Oct 1, 2021

Choose a reason for hiding this comment

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

try compiling that - I'm guessing resharper is just getting confused- for example:

foreach (var kvp in a.InportCountMap)

and an example:
https://dotnetfiddle.net/nekXuD

ah my mistake, OrderedDictionary does not return KeyValuePairs when iterating over them - it returns DictionaryEntries https://docs.microsoft.com/en-us/dotnet/api/system.collections.dictionaryentry?view=net-5.0

instead of var examples on MSDN show using a foreach look like this:

foreach (DictionaryEntry de in myOrderedDictionary)
{
    //...
}

/// <summary>
/// A static class containing methods for dynamically building a Node's Context Menu.
/// </summary>
internal static class NodeContextMenuBuilder
Copy link
Member

@mjkkirschner mjkkirschner Sep 28, 2021

Choose a reason for hiding this comment

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

  1. please use nameof where you can, when these strings change, everything will break.
  2. There is a lot of work in here to construct the views and bindings with code - thats fine - but I think the xaml could also have been reused by extracting it to a resource and instantiating it - perhaps that would not play well with the dynamic menu items - but I think if we can avoid re-writing all of our xaml we should. Consider if this is useful: https://docs.microsoft.com/en-us/windows/apps/design/style/xaml-resource-dictionary#look-up-resources-in-code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  1. I've inserted nameof everywhere. The only hard-coded strings in the class are now equivalents of those that were previously hard-coded in XAML.
  2. I'm afraid I don't understand this approach. Please feel free to reach out on Slack/Zoom if you want to go over this.

@mjkkirschner
Copy link
Member

This PR should have tests for the new builder class if you decide to go that way.

@OliverEGreen
Copy link
Contributor Author

This PR should have tests for the new builder class if you decide to go that way.

Added 5 tests to cover the new functionality.

@QilongTang
Copy link
Contributor

The regressions reported for this PR should be unrelated

Test Result (3 failures / +3)
DynamoCoreWpfTests.PackageManagerUITests.CannotCreateDuplicatePackageSearchDialogs
DynamoCoreWpfTests.PackageManagerUITests.CanOpenPackageSearchDialogAndWindowIsOwned
DynamoCoreWpfTests.PackageManagerUITests.PackageSearchDialogClosesWithDynamo

Comment on lines +381 to +387
internal static void AddInjectedNodeViewCustomizationMenuItems(OrderedDictionary nodeViewCustomizationMenuItems)
{
foreach (DictionaryEntry keyValuePair in nodeViewCustomizationMenuItems)
{
ContextMenu.Items.Add(keyValuePair.Value);
}
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mjkkirschner I think in the latest version of the files there's a DictionaryEntry in this foreach loop. Not entirely sure why the PR isn't showing this on the main page! Must be outdated.

@mjkkirschner
Copy link
Member

@OliverEGreen your PR still changes ContextMenuStyle to NodeContextMenuStyle causing test failures because that style is used in other places. I believe that change was just reverted by @QilongTang - is there a reason you are bringing it back in?

Copy link
Member

@mjkkirschner mjkkirschner left a comment

Choose a reason for hiding this comment

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

please see above comment regarding: ContextMenuStyle

@OliverEGreen
Copy link
Contributor Author

@OliverEGreen your PR still changes ContextMenuStyle to NodeContextMenuStyle causing test failures because that style is used in other places. I believe that change was just reverted by @QilongTang - is there a reason you are bringing it back in?

Ah I hadn't spotted it was being used elsewhere. No reason to change the name then! Can undo this on Monday. Thanks for raising!

@QilongTang
Copy link
Contributor

QilongTang commented Oct 7, 2021

The following regression reported but I think those were fixed on master in a previous PR. Seems you noticed the styling change already

DynamoCoreWpfTests.PackageManagerUITests.CannotCreateDuplicatePackageSearchDialogs
DynamoCoreWpfTests.PackageManagerUITests.CanOpenPackageSearchDialogAndWindowIsOwned
DynamoCoreWpfTests.PackageManagerUITests.PackageSearchDialogClosesWithDynamo

@QilongTang QilongTang merged commit 2212221 into DynamoDS:master Oct 12, 2021
@OliverEGreen OliverEGreen deleted the node_lazy_context_menu branch November 9, 2021 09:28
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.

5 participants