Factoring out complex delegate - has slowed down
-
I am looking for advice on how to go about factoring out some fairly complex delegate code that is currently embedded directly in a
ListView-based component. I would like to be able to reuse the delegate in a slightly differentListViewor, potentially, a different view altogether. I have done a preliminary refactor that sort of works but has slowed it down considerably for reasons I don't fully understand. I would like advice on whether I have gone about the refactor in the right way and also whether there is anything obvious that could be causing the slowdown.My
ListViewcomponent contains a table of settable values of different types. It is driven by a model that provides information such as the name of each item, its data type, its current value, etc. Because the entries are of different types, they need different controls to display and edit the values. The data type determines the type of control to use for each entry. This is based on using aLoaderin the delegate, with a switch on the data type to select the correct component. (I am currently constrained to use Qt 5.9.6 - not for much longer I hope - so do not have access to DelegateChooser.)This is one of the first things I wrote when I started with QML and it has grown somewhat "organically" in the meantime. All of the code is currently inside the
ListView-based component. I now want to be able to reuse all of the delegate code so I essentially want to factor the delegate code out of theListViewcomponent.This is the structure at the moment:
ListView { ... model: myModel ... // Various helper functions used in the delegate function deriveType(...) { ... } function anotherHelper(...) { ... } ... // This is used by some of the property type Components below MyFileDialog { id: dlg } Component { id: intDelegate IntControl { ... } // text field geared to integer input } Component { id: optionDelegate OptionControl { ... } // essentially combo with string options } Component { id: fileDelegate FileControl { // selects a file path dialog: dlg // uses the common dialog above ... } } ... delegate: RowLayout { Label { text: label // 'label' from model } Loader { // Loaded components don't get automatic access to the model // fields so need to copy into new variables here that will // be in the scope of the loaded components. // Various massaging of the data goes on here too using the // helper functions. property loader_type: deriveType( /* access model fields */ ...) property ... ... switch (loader_type) { case "int": return intDelegate; case ... } } }Virtually everything I have shown above, the
Components, the helper functions, the delegate,
are all related to the delegate implementation so I want to factor this out into a separate component.This is what I have done (in
ValueDelegate.qmlsay):Item { // Various helper functions used in the delegate function deriveType(...) { ... } function anotherHelper(...) { ... } ... // This is used by some of the property type Components below MyFileDialog { id: dlg } Component { id: intDelegate IntControl { ... } // text field geared to integer input } ... ... // as above RowLayout { // exactly as the delegate in the original code above Label { ... } Loader { ... } }whereas the
ListViewis now very small and looks more like this:ListView { model: myModel ... delegate: ValueDelegate { } }I have only done a rudimentary factoring out so far. I should probably define properties at the
Itemlevel to receive the model fields. At the moment these are visible to theRowLayoutinValueDelegatebecause the context in which theValueDelegateis created in theListView.Anyway, this works after a fashion but it is extremely slow. This table is filled based on selections elsewhere in the GUI. When a new selection is made, the model changes, the table is effectively cleared and refreshed. Before this refactor, this was very snappy, but now I am waiting a couple of seconds.
One thing I am wondering is whether it is because the delegate is now a lot "heavier" than it was before. The delegate is now this
Itemthat has all the helper functions, theComponents for the value types as well as the originalRowLayout. Whereas in the original code a lot of that existed at theListViewlevel, it is now all in the delegate itself. I don't have a feel for what sort of overhead this introduces.If it is likely that the delegate is too heavyweight now, is there a better approach I could take to refactoring this?
-
I am looking for advice on how to go about factoring out some fairly complex delegate code that is currently embedded directly in a
ListView-based component. I would like to be able to reuse the delegate in a slightly differentListViewor, potentially, a different view altogether. I have done a preliminary refactor that sort of works but has slowed it down considerably for reasons I don't fully understand. I would like advice on whether I have gone about the refactor in the right way and also whether there is anything obvious that could be causing the slowdown.My
ListViewcomponent contains a table of settable values of different types. It is driven by a model that provides information such as the name of each item, its data type, its current value, etc. Because the entries are of different types, they need different controls to display and edit the values. The data type determines the type of control to use for each entry. This is based on using aLoaderin the delegate, with a switch on the data type to select the correct component. (I am currently constrained to use Qt 5.9.6 - not for much longer I hope - so do not have access to DelegateChooser.)This is one of the first things I wrote when I started with QML and it has grown somewhat "organically" in the meantime. All of the code is currently inside the
ListView-based component. I now want to be able to reuse all of the delegate code so I essentially want to factor the delegate code out of theListViewcomponent.This is the structure at the moment:
ListView { ... model: myModel ... // Various helper functions used in the delegate function deriveType(...) { ... } function anotherHelper(...) { ... } ... // This is used by some of the property type Components below MyFileDialog { id: dlg } Component { id: intDelegate IntControl { ... } // text field geared to integer input } Component { id: optionDelegate OptionControl { ... } // essentially combo with string options } Component { id: fileDelegate FileControl { // selects a file path dialog: dlg // uses the common dialog above ... } } ... delegate: RowLayout { Label { text: label // 'label' from model } Loader { // Loaded components don't get automatic access to the model // fields so need to copy into new variables here that will // be in the scope of the loaded components. // Various massaging of the data goes on here too using the // helper functions. property loader_type: deriveType( /* access model fields */ ...) property ... ... switch (loader_type) { case "int": return intDelegate; case ... } } }Virtually everything I have shown above, the
Components, the helper functions, the delegate,
are all related to the delegate implementation so I want to factor this out into a separate component.This is what I have done (in
ValueDelegate.qmlsay):Item { // Various helper functions used in the delegate function deriveType(...) { ... } function anotherHelper(...) { ... } ... // This is used by some of the property type Components below MyFileDialog { id: dlg } Component { id: intDelegate IntControl { ... } // text field geared to integer input } ... ... // as above RowLayout { // exactly as the delegate in the original code above Label { ... } Loader { ... } }whereas the
ListViewis now very small and looks more like this:ListView { model: myModel ... delegate: ValueDelegate { } }I have only done a rudimentary factoring out so far. I should probably define properties at the
Itemlevel to receive the model fields. At the moment these are visible to theRowLayoutinValueDelegatebecause the context in which theValueDelegateis created in theListView.Anyway, this works after a fashion but it is extremely slow. This table is filled based on selections elsewhere in the GUI. When a new selection is made, the model changes, the table is effectively cleared and refreshed. Before this refactor, this was very snappy, but now I am waiting a couple of seconds.
One thing I am wondering is whether it is because the delegate is now a lot "heavier" than it was before. The delegate is now this
Itemthat has all the helper functions, theComponents for the value types as well as the originalRowLayout. Whereas in the original code a lot of that existed at theListViewlevel, it is now all in the delegate itself. I don't have a feel for what sort of overhead this introduces.If it is likely that the delegate is too heavyweight now, is there a better approach I could take to refactoring this?
Seconds after writing all that and posting it, it occurred to me to try one thing, which was to remove the file dialog from the delegate and to pass that in instead. That appears to have solved the speed issue.
Still interested to hear whether the approach seems OK otherwise - is it OK to have all those embedded
Components in the delegate and is there any alternative?So I now have something more like this:
ValueDelegate.qml:Item { property dlg // **remove embedded MyFileDialog from delegate** // everything else the same...The
ListView:ListView { model: myModel ... MyFileDialog { id: myDlg } delegate: ValueDelegate { dlg: myDlg } }