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

Addition of Automatic Node Layout Option #83

Closed
Sage-of-Mirrors opened this issue Aug 10, 2020 · 6 comments
Closed

Addition of Automatic Node Layout Option #83

Sage-of-Mirrors opened this issue Aug 10, 2020 · 6 comments

Comments

@Sage-of-Mirrors
Copy link

I have come across an annoying issue with regards to arranging programmatically-created nodes within the graph. It appears that the nodes must be rendered before their sizes are properly set, which means that I cannot properly arrange the nodes because their sizes are report as 0 in both dimensions.

I was able to properly arrange a node network by hooking the ContentRendered event on the window it occupies, but the window contains several networks in different tabs. Only the first network, the one that had focus be default, reported proper node sizes; the unfocused networks still had the default 0 sizes. I could not find any event exposed by NetworkView that gave me access to a state where the node sizes of all networks were properly set, and forcing a layout update via the Invalidate functions did not work either.

If there is not a simpler solution to this issue, I would like to suggest an addition to the NetworkView, which I am happy to make a pull request for: a Func<NetworkViewModel> property to contain a function for laying out the nodes, and a bool property to tell whether the network should automatically run the layout function when NetworkView's OnRender() is called. This could possibly mesh with the current layout code in the Toolkit, perhaps by making the Func a generic layout object instead.

@Wouterdek
Copy link
Owner

I see, that is indeed a problem. The activation/deactivation event of the networkview might be the trigger you are looking for. If not, a temporary workaround could be to observe changes on the width and height of a node and see when they become non-zero, but that's ugly.

I'll be working on the library at the end of this month or the beginning of september, I'll look into it then.

@Wouterdek
Copy link
Owner

Ok, so it seems there is no ContentRendered for UserControls, only for Windows. The best alternative is to work with the SizeChanged or LayoutUpdated events, but at that point you might as well just observe the Size property of the nodes.

Here is my suggestion:

myNetwork.Nodes.Connect()
   .AutoRefresh(node => node.Size)
   .Where(node => node.Size.Width > 0 && node.Size.Height > 0)
   .Count()
   .Select(sizedNodeCount => sizedNodeCount > 0)
   .DistinctUntilChanged()
   .Subscribe(sizedNodesVisible =>
   {
      // Do layouting here if sizedNodesVisible == true
      // Optionally, you might want to add 
      // Where(sizedNodesVisible => sizedNodesVisible == true).FirstAsync()
      // above to observe only the first time a node with non-zero size is visible.
   });

Important note: make sure you include using DynamicData.Aggregation;, otherwise the compiler will use the wrong version of .Count() and the snippet above will not work.

@Sage-of-Mirrors
Copy link
Author

This seems to work! However, as it's written, it will only trigger when the first node in the network viewmodel receives a size. I made the first .Where statement into:

.Where(node => node.Size.Width > 0 && node.Size.Height > 0 && node == model.Nodes.Items.Last())

And this looks like it always triggers once the last node in the viewmodel has a size.

@Sage-of-Mirrors
Copy link
Author

Encountered another issue: The nodes always report a Size of (120, 42) even if they should be bigger, e.g. if they have embedded controls or longer names.

@Wouterdek
Copy link
Owner

Right, so it seems WPF updates the size of elements a bunch of times until settling on a final value, after which LayoutUpdated is triggered.

Does this produce the correct size for you?

networkView.Events().LayoutUpdated
                .Where(_ => networkView.ViewModel != null && networkView.ViewModel.Nodes.Count > 0)
                .Select(_ => networkView.ViewModel.Nodes.Items.Last().Size)
                .Where(size => size.Width > 0 && size.Height > 0)
                .FirstAsync()
                .Subscribe(size => { Debug.WriteLine("size: "+size); });

(.Events() is part of the ReactiveUI.Events.WPF package)

@Sage-of-Mirrors
Copy link
Author

Looks like that does the trick! Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants