-
Notifications
You must be signed in to change notification settings - Fork 635
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
Lazy Load Node Context Menu #12074
Conversation
@OliverEGreen is it possible to include the benchmarking numbers you have in the description here? Before and After 😄 |
|
<ContextMenu Name="MainContextMenu" | ||
x:FieldModifier="public" | ||
Closed="MainContextMenu_OnClosed" | ||
Style="{StaticResource NodeContextMenuStyle}"> |
There was a problem hiding this comment.
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:
-
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?
-
Why does this style include references to the dismissed badge, is that in the context menu at certain times?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 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.
- Yes it is! It's there at all times.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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>(); |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
??
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var
There was a problem hiding this comment.
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); }
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- please use nameof where you can, when these strings change, everything will break.
- 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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- 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.
- 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.
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. |
The regressions reported for this PR should be unrelated
|
internal static void AddInjectedNodeViewCustomizationMenuItems(OrderedDictionary nodeViewCustomizationMenuItems) | ||
{ | ||
foreach (DictionaryEntry keyValuePair in nodeViewCustomizationMenuItems) | ||
{ | ||
ContextMenu.Items.Add(keyValuePair.Value); | ||
} | ||
} |
There was a problem hiding this comment.
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.
@OliverEGreen your PR still changes |
There was a problem hiding this 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
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! |
The following regression reported but I think those were fixed on master in a previous PR. Seems you noticed the styling change already
|
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
*.resx
filesReviewers
@saintentropy
@mjkkirschner
@QilongTang
FYIs
@Amoursol