Monitor all Components for changes


#1

Since a Juce application is a hierarchy of components, can I monitor all child components in the TopLevelWindow for any state change - movement, size change, visibility etc?

I need to monitor and record any state change of any component in my application so I can play back the changes later from the same initial state.


#2

couldn’t you use a ValueTree that stores all of those component parameters you need to monitor/record along with an UndoManager?


#3

Sure you can be a ComponentListener see https://www.juce.com/doc/classComponentListener


#4

Thanks for the replies.

I have used the ValueTree approach and that does work. Also I have tried the ComponentListener.

ComponentListener does not seem to be capable of listening to changes in the child components, clicks on them or setText() and setSelected() related changes.

For ValueTree, I would still need to use Listeners and listen to the components before being able to record the change in a ValueTree property. Unless I know what occurred how would I record it? So I would have to derive that component from all manner of Listeners using multiple inheritance and it would still not cover the entire breadth of state changes that could occur(such as color change)

That’s a bit tricky to do if I already have a large bunch of Components to which I have to now add this new functionality.

I was hoping for something more out of the box. Since Juce does know about all state changes that happen, it could provide users access to that information by registering a full fledged, no-holds-barred listener.
Maybe it can put all that information into a queue of some sort if the user requests it and provide users that information.

In fact it could ship with a undo-redo class that can be attached to any Component. The class would record all state changes on the Component or its children and provide APIs for undo-redo.

In that sense a Juce application would just be state data that is being edited as the application runs :slight_smile:


#5

Ouch! No, no! That’s pretty much the exact opposite of the way you should be building an app!

Think of components as things that reflect the state of your app’s data model. You should never, ever, treat them as a representation of state themselves.

The listening must all bel the other way round - your components listen for changes to your data model, and update themselves when it changes. When the user interacts with a component, it must modify the data model, and allow that change to be picked up and updated.

A good rule of thumb is that if you’ve designed your app correctly, you should be able to delete all your GUI objects at any time, and then easily recreate them without anything being lost. And ideally you should be able to have 0, 1, or many instances of your GUI running on the same data model without that being a problem.


#6

You would need to register as a ComponentListener, a MouseListener, and maybe also try dynamically casting to some other component types so you can register as other listener types. To do this for all components shouldn’t be too difficult, it just takes some recursion, then you need to add / remove yourself as a listener based on the call backs.

The tricky bit is playing back any recorded changes, if you need to play back the changes at a later date, say if you close and reopen the application then you will need some way to identify each component individually - either you will have to build up a hash code that uniquely identifies each component based on some properties about it or make sure every component is uniquely named. What will it do if a component is in a different position with a different size, or starts with different visibility? If you only need to playback while running that instance it will be easier but still there will be gotchas. For example I’m not sure what would be the best way to handle a component being deleted! How are you going to add it back again on a redo? But for…

movement, size change, visibility etc?

…it should be just fine.

What are you actually trying to achieve? if you want user interaction to be recorded and played back then I think you only really need to record mouse and keyboard events and play those back, then let the components control their size, position, visibility, etc. I recently started a project doing this with mouse events maybe I’ll finish it and put it up at some point, but I found myself wanting every component to be uniquely named so that when I played back mouse events, if components had moved I wanted the event to be played back relative to the new position of the component. I hadn’t got to keyboards events on that project, I suspect this will be harder but I might be wrong.

If you do want to listen to mouse events then adding yourself as a listener of all nested children is a little easier, just do something like componentToWatch.addMouseListener (this, true);

Which reminds me, you should be able to record all mouse events except mouseMagnify(). @jules is there any reason why mouseMagnify() isn’t part of the MouseListener class?


#7

No… probably just be an oversight when it was added.


#8

Yeah using MVC is the right way to do it. Unfortunately that pattern is not yet fully available in this application though we are gradually refactoring our code towards that.

Meanwhile we do need to record button clicks, text typing, label clicks, slider changes etc. and then store them in some sort of text file. Later all these actions need to be played back.

Currently I am recording various actions in the event handlers like buttonClicked() or comboBoxChanged() and then playing them back. I am able to uniquely locate items through ‘paths’ that I capture when recording. I can use the paths later to locate the Component correctly.

It works in most cases for example I can simply call click() on buttons to reproduce the original click Or setText() or setSelectedItemId() for text boxes and combos.

But there are some edge cases e.g. for clicks on labels, even if I record it, playing it back by calling click() on the label component does not work: Label::click() does not generate a mouseUp() call