Jump to content

Copying change from an individual cluster element to multiple clusters


Recommended Posts

Basic problem:
If an element of a given cluster is changed - that value, and that value change alone, should be applied to several other clusters (which will otherwise retain their other element values).

Known solutions:
In the attached file (LabVIEW 2020 code) I have two ways of doing this: 1) using dynamic events and Set Cluster Elements by Name (OpenG), or 2) by using the Cluster To Array of VData.vi (also from OpenG).  (Note: The use of array locals in the examples are there just to simplify the demo).

Question:
Are there other / better ways of doing it?

 

A bit more background:
In configuration windows I sometimes allow users various ways to choose to apply any *changes* globally or to a selection of targets. If e.g. they want to change the baud rate of 3 out of 5 different serial links they can choose the 3 links, and if they change anything in the communication setup of the first of them it will be applied to all 3 links (the rest of their setup will remain as before (they might still have different parity settings e.g.).  I usually avoid clusters in the GUI so each change is handled individually, either in a dedicated value change event case (lots of coding needed...), or in a common case that uses the control reference to touch the correct element of the other target's configuration...(less code needed). However, in cases where the number of controls is very high, it can be nice to just have them in a cluster in the GUI as well - and to not have to write event cases to handle the change of each and every cluster element...

Distributing cluster changes.zip

 

Edited by Mads
  • Like 1
Link to comment

Two suggestions:

1) Consider using JSON as your config-data format, rather than clusters.  Using JSONtext to manipulate JSON will be faster than using OpenG tools to manipulate clusters.  

2) Programmatically get an array of references to all your config-window controls and register a single Event case for Value Change of any one of them.  Then use their (hidden) labels to encode what config item they set.  For example, your control with the caption "Baud Rate" could have the hidden label "$.Serial Settings.Baud Rate" which is the JSONpath to set in your config JSON (or config clusters).

  • Like 2
Link to comment

Good advice.  I just wanted to add that I was using the OpenG INI stuff for years because of how easy it was to save and load a panel to a section of an INI file.  I'd have a set of pages as separate VIs that get loaded into a subpanel, and it was easy to just save and load the whole panel to a file.  This started to have lots more functionality and these panels could run and have tables or trees that would eventually get saved as arrays of clusters in the INI file.  I eventually found that the performance from these OpenG functions was quite poor when there are arrays of clusters of arrays of clusters since each I think line in the file was a separate call to the NI INI VIs.  At the time I hacked in a way to write the data in a flattened, non-human readable format for these large data types.  The MGI Read/Write Anything is much better performance-wise due to a rewrite of how it works.

If I were to start over today I would consider JSON instead.

Link to comment
2 hours ago, drjdpowell said:

Two suggestions:

1) Consider using JSON as your config-data format, rather than clusters.  Using JSONtext to manipulate JSON will be faster than using OpenG tools to manipulate clusters.  

2) Programmatically get an array of references to all your config-window controls and register a single Event case for Value Change of any one of them.  Then use their (hidden) labels to encode what config item they set.  For example, your control with the caption "Baud Rate" could have the hidden label "$.Serial Settings.Baud Rate" which is the JSONpath to set in your config JSON (or config clusters).

This is brilliant. I imagine it could even become a standalone toolkit/extension of JSONtext.

Link to comment
2 hours ago, drjdpowell said:

Two suggestions:

1) Consider using JSON as your config-data format, rather than clusters.  Using JSONtext to manipulate JSON will be faster than using OpenG tools to manipulate clusters.  

2) Programmatically get an array of references to all your config-window controls and register a single Event case for Value Change of any one of them.  Then use their (hidden) labels to encode what config item they set.  For example, your control with the caption "Baud Rate" could have the hidden label "$.Serial Settings.Baud Rate" which is the JSONpath to set in your config JSON (or config clusters).

I have not used JSON/JSONtext  much, but this sounds like a good plan.

If you can bare with me, could you elaborate a bit on how this can be implemented (or point me to an example/documentation that would help)?

To use the solution examples I posted as a starting point: Would I replace the array of clusters with a JSON object? In JSONtext, how do I go about replacing the value of multiple items then?

If I start with this (instead of the LabVIEW cluster array from the examples):

[
  {"Enabled":true,"Hello":-2,"Hi":6},
  {"Enabled":true,"Hello":-2,"Hi":6},
  {"Enabled":true,"Hello":-2,"Hi":6},
  {"Enabled":true,"Hello":-2,"Hi":6}
]

and try using Set item with the path $.[*].Enabled e.g. when that value changes, it returns an error. Accessing just one of the indexes ($.[0].Enabled e.g.) fails as well. What would be the correct way? Naming each element to be able to use set multiple items with an item names input?

(I did successfully rewrite the demo to work with JSON using JSONtext, but then I just treated each array element as a separate JSON string and ran Set item on each of them with the path $.<Label.text>😳)

 

PS. I guess you would also use your SQLite library to store the configuration of each device in a table with the config clusters as JSON strings then...or?🙂

 

Edited by Mads
Link to comment

Sorry to chime in, maybe I had a similar problem some time ago and I'm interested in the GUI design part of the problem.

So basically you have a config window and you only want to apply changes to elements that were changed by the user? And the changes should affect more things (all changed cluster elements in an array of clusters)? How the users know what was changed besides their memory? How would a user apply an elements current (default) value to the other clusters apart from changing the value than changing back to the default?

If I'm totally off, then sorry. I kind of gave up on this feature because I coudn't think of a proper solution that would be evident to use for the users apart from the two workarounds I mentioned or having a "changed?" or "apply value?" bool control next to every single control.

Link to comment

As a user I'm not entirely sure I need to know what control values I changed.  Some other software does have a visual indicator of a control value changing from the default.  But most of the time, like in the LabVIEW config dialog, I just change all the things I want to, and then click OK or Apply or whatever.

But if you do really want to have some kind of visual indicator of a change, maybe you could have a higher level indicator.  In my software I mentioned I have several Pages, each of which is a VI, with a listbox on the left displaying them all, and a subpanel on the right for whichever is selected.  In my software if the user clicks Apply or Ok, it will figure out what pages have been changed, and then publish to a public event that a config change has taken place, and what pages were changed.  This way the software can be told if something changed that the code module needed.  If the power supply page was changed, odds are the power supply actor is going to need to reload the config, and the logging actor doesn't.  If the debug page is changed, more actors are going to need to know about it and reread the new config.  So along those lines I bet you could have a visual glyph in the listbox, that shows that there are unapplied changes in that page.  It won't go down to the control level but might be handy as a reminder of the pages the user changed settings on.

Oh and I haven't needed this but I wonder if you could have a "Load Default" either for each page, or a right click option on the listbox.

Link to comment
1 hour ago, Lipko said:

Sorry to chime in, maybe I had a similar problem some time ago and I'm interested in the GUI design part of the problem.

So basically you have a config window and you only want to apply changes to elements that were changed by the user? And the changes should affect more things (all changed cluster elements in an array of clusters)? How the users know what was changed besides their memory? How would a user apply an elements current (default) value to the other clusters apart from changing the value than changing back to the default?

If I'm totally off, then sorry. I kind of gave up on this feature because I coudn't think of a proper solution that would be evident to use for the users apart from the two workarounds I mentioned or having a "changed?" or "apply value?" bool control next to every single control.

The GUI presents a list of devices by tag in a list box or tree where the user can choose to select one or multiple devices (only of the the same type). If they choose multiple that is one way for me /the code to know whether they want to have changes replicated. If they have selected multiple serial links e.g. the GUI will only show the setup of one link, but changes will be applied to all. There is also a tick box that allows them to put the GUI into "global mode", which is just a shortcut to always apply all changes to all devices of the same type (no need then to select them manually in the listbox/tree).

As you say there is one issue with this, and that is if they want to apply a value that is already set for the device chosen by the GUI as the "template". They then have to re-enter the value just to trigger the distribution of it to the other device configurations. This I find OK though - it is part of the "contract"; when you select global mode or multiple devices the user accepts that this will apply *changes* to all - and that is that. With that contract in their head the function makes sense / is intuitive enough. You could add additional GUI elements like an apply all button to sub-sections of the configuration, it depends on how many elements you have in the setup and how often the user would want to replicate them. In my case there are many inputs, but only some of them make sense to replicate; but those often need to be replicated to *many* devices, so the user saves a lot of time having this feature (makes users very happy to have it, so no complaints about such shortcomings... 🙂 ). I do not indicate to the user which values he has changed, that I expect the user to keep track of himself...(he knows what he just did, if he has forgotten already he can always cancel the changes though) but as @hooovahhmentions you could do that...


One thing I have not mentioned is that the replication/apply to many/all functionality is kept from touching things that should be unique anyway... Guarding from such changes can be put at different levels, in my case they it is handled by having dedicated event cases for those controls, but in a more generic solution you might want to have a blacklist instead, or include a key in the control name that enabled/disables the replication...

Edited by Mads
Link to comment
2 hours ago, hooovahh said:

Oh and I haven't needed this but I wonder if you could have a "Load Default" either for each page, or a right click option on the listbox.

The SQLite API for LabVIEW had a feature for that. Very easy with a database.

image.png.b73afb4db1606a7244e2d6145f9d967e.png

image.png.2b848bc8e7a3760c689cbe7726b4484b.png

I suppose you could do something similar just by saving and loading a particularly named JSON file.

Edited by ShaunR
  • Like 2
Link to comment

I solved it by having multiple dialogs, so the user can only change a single thing or a few closely related things at once. There is still a god dialog but users find it hard to "feel" that all its elements will be applied on all selected "sections". In my usecase the settings mean properties of a section in a sequence (an array of clusters is a sequence and the dialog shows all elements of that cluster or section). In most cases only a few things will be the same in multiple sections, for example the comment, or section length, but the god dialog was a hystorical thing (the only way to set up a section in the first release) that I left there because many users are so used to it. I never got back to the problem, the best idea I could come up with is to change the background of an element if it was changed by the user, but didn't implement it. I still feel the multiple single-property dialogs are more intuitive and also quite simple to implement, and maybe it would be the best just to get rid of the god dialog. But this doesn't apply to the original post, I don't fully understand that usecase.

Link to comment
8 hours ago, drjdpowell said:

For example, your control with the caption "Baud Rate" could have the hidden label

The use of the caption for text presentation instead of the label is, perhaps, a little understated as to it's usefulness. You can use labels to identify single controls or groups of controls by using a consistent naming convention (e.g. Button_1, Button_2 etc). By adding the VI name, this becomes a method of name-spacing controls across an entire application. This was how PassaMak (an old, on-the-fly, translation tool of mine) operated and how the Panel Settings Example above is supposed to be used.

Link to comment
17 hours ago, Mads said:

and try using Set item with the path $.[*].Enabled e.g. when that value changes, it returns an error. Accessing just one of the indexes ($.[0].Enabled e.g.) fails as well.

You should be using $[*] or $[0] to indicate Array elements; $.[*] indicates all items in a JSON Object and $.[0] is the Object item named "0".  Look at the detailed Help page for JSON Path notation in JSONtext.

  • Like 2
Link to comment
17 hours ago, Mads said:

I did successfully rewrite the demo to work with JSON using JSONtext, but then I just treated each array element as a separate JSON string and ran Set item on each of them with the path $.<Label.text>

That would be a perfectly good choice.  You could also use an array of paths like '$[n].Enabled' and call Set Item in a loop.  

Aside: Note that you could also use a JSON Object rather than an array to hold your list of targets.  Like this:

{
  "My Inst X":{"Enabled":true,"Hello":-2,"Hi":6},
  "My Inst Y":{"Enabled":true,"Hello":-2,"Hi":6},
  "My Inst Z":{"Enabled":true,"Hello":-2,"Hi":6},
  "My Other Instrument":{"Enabled":true,"Something Else":true}
}

I mention this as I have noticed that LabVIEW programmers often get stuck in a mental model of JSON Objects and Arrays mapping onto LabVIEW Clusters and Arrays.  In particular note that JSON Objects are not fixed type and number or elements at edit time (unlike Clusters), and JSON Arrays are not restricted to all elements having the same type (like LabVIEW Arrays).

I also made the last instrument in my example one with a differences in its config structure.  By using JSON, you can still let your User change common settings (like "Enable") even with instruments that cannot be represented as the same LabVIEW Cluster. This is an example of "Duck Typing", which can be difficult to do in LabVIEW otherwise.

Edited by drjdpowell
Link to comment
13 minutes ago, drjdpowell said:

You should be using $[*] or $[0] to indicate Array elements; $.[*] indicates all items in a JSON Object and $.[0] is the Object item named "0".  Look at the detailed Help page for JSON Path notation in JSONtext.

Aah, thanks, that works.😃 I made a demo of the issue, but you replied just when I was about to post it.

The reason I though the $.[0] notation should work was that if I tested it with https://jsonpath.com/ it (almost) seems to find what I wanted...

Attached is the demo/test I made...but now with the correct notation for arrays as well.

Exploring having Cluster array as JSON.vi

Link to comment
16 minutes ago, Mads said:

The reason I though the $.[0] notation should work was that if I tested it with https://jsonpath.com/ it (almost) seems to find what I wanted...

JSONpath doesn't have a specification, unfortunately, and so you can't really rely on different implementations matching on edge cases.  I also haven't implemented everything (just due to teh effort required that I don't have time for).

Link to comment
4 hours ago, drjdpowell said:

JSONpath doesn't have a specification, unfortunately, and so you can't really rely on different implementations matching on edge cases.  I also haven't implemented everything (just due to teh effort required that I don't have time for).

No complaints, you are doing a fantastic job 😀👍 

I edited my examples to include two versions based on JSONtext (attached).

PS. I have not tried to scale up the cluster array to see what the differences would be performance-wise yet, they all have their pros and cons usability-wise though.

Distributing cluster changes, now with 4 solutions.zip

Edited by Mads
Link to comment
  • 1 month later...

I ended up getting too frustrated (again) with the stupidity of having clusters in the GUI involved in key navigation and locked to one terminal . so all controls are individual/primitives in the GUI again. Then the challenge however is that initializing all those individual controls is an inflexible and space-eating affair when done in the traditional graphical way (unbundling extra large clusters).

So:
How to programmatically fetch all values from a huge cluster and initialize all equivalent individual controls in the GUI?

Well, one way I had been using before was to use a Ctrl Val.Set property. That works fine as long as the names of the individual controls in the cluster(s) are unique and match the labels of the controls in the GUI....but you then have to "flatten" the cluster to a list of names and variants. If the cluster(s) do not contain subclusters I use this OpenG-based method:

image.png.ed6641e467111ac14276cce21612b83e.png

But in this case the cluster is complex - and there are sub-clusters within subclusters, something this code does not handle. The OpenG configuration VIs handle this in "Write INI cluster", but not in a way directly translatable to a list of names and variants as the recursive algorithm to access sub clusters does not translate into this use case....

So I wrote the attached VIs to flatten any complex cluster into its individual element names and variants:

image.png.b766b1860699494144f5026720020efc.png

The initialization code could alternatively be put into the VI itself instead, and perhaps a blacklist/only init these input could be added, but for now this does the job: It keeps the initialization code very compact and flexible - even though the cluster(s) in question is complex and might change later (sorry, did not rewrite it to JSON instead...not yet at least @drjdpowell😳)

Is anyone else doing something similar, perhaps better?
 

Demo of cluster to init of individual controls.zip

Edited by Mads
Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.