how to simplify convoluted UI signals?
-
My application displays three UI components: A,B,C. A and B are TableView, C is Map. B and C display the same data but in different form. A changes the source of data.
Now I am writing quick tests for that. However, this is too difficult. I think I might have made signal connections unnecessarily convoluted. This is especially apparent for components B and C. User can interact with one and the other is automatically updated. It is difficult to avoid recursion between B and C.
I need to simplify those bindings. Are there any good practices or design patterns I could use?
-
-
Components B and C present locations on a map. B lists names in TableView (ordinal nr | name). C puts a mark (ordinal nr) on a map for each location. User can click a name in table or a mark on map. A location is selected when both the mark and the name are highlighted.
Let's assume there are two signals itemClicked and itemSelected defined for each B and C. For a moment I had badly chosen connections below.
A.itemClicked raises A.itemSelected (color item and notify)
A.itemSelected raises B.itemClicked (notify B)
B.itemClicked raises B.itemSelected (color item and notify)
B.itemSelected raises A.itemClicked (notify A)
..... (recursion)To fix it I got rid of itemSelected.
A.itemClicked then B.toggleItem()
B.itemClicked then A.toggleItem()Now I am not satisfied with the design any more. I would like to add a TestCase with SignalSpy to track my application. I have to refactor my QML so that it is testable and makes sense. I think I should bring signals back but obviously not the bad connections above. I wonder if there is a common practice / design patter I could employ here.
-
@mtty said in how to simplify convoluted UI signals?:
I wonder if there is a common practice / design patter I could employ here.
Yes, it is to define a master/maiin object that will receive signals from all others (child) objects of the application.
This way, when the main object - usely the top level object (main window) - receives a signal from the child objects (in your case A B C), it updates the interface accordingly (it knows how to do it right).
And good pratices dictate to avoid dependencies, that means A B C shoud know nothing about each others, that will prevent spaghetti code you’re facing right now ;) -
-
@DerReisende I like the example. However, QSignalBlocker is not available in QML.
I might delegate "clicked" signal to a mediator object (an instance of QtObject in QML). The mediator then calls "select" method on map component and table component. Only after that "selected" signal is emited; no signal blocker needed. That would have work, if only TreeView had not gave ItemSelectionModel freedom to "deselect" itself (click on TableView where there is no delegate).
In order to use QSignalBlocker I have to somehow attach C++ class to already defined QML component. It was very handy to develop those components in QML. But now I have to convert QML component back to a C++ class? Just to make use of QSignalBlocker in signal handler?
In Qt qml examples there is tutorial with PieChart C++ class and QML element. It is great. But it is simple. Do I really have to convert my QML component with customized TableView QML to raw C++ just to get that QSignalBlocker?
-
In the end I refactored mediator QtObject from QML into a C++ QObject. However, I am surprised
sender()
returnsNULL
. Maybe it is so because I connect slots in QML withConnections
on mediator instance set as a context property. Perhaps if I were to useQObject::connect()
in C++ instead,sender()
might have worked as expected. -