I'm currently building a data aquisition application, in which UI I placed the controls for mutually exclusive aquisition modes in the different subpages of a QToolbox, where the currently selected QToolbox item selects the aquisition mode parameter.
One desired feature is to lock the UI – after changing the parameters, the changes have to be applied/confirmed, being able to lock the UI elements against accidental changes is a feature, implemented by simply disabling the widgets. And since the aquisition mode is one parameter this also means locking down the QToolbox. However some controls within the toolbox need to stay unlocked since their effect is immediate.
So here's the question: In Qt4, how can I keep child widgets enabled and accessible while their container has been disables. Or how can I emulate the behaviour and look of such a setup?
Please don't suggest changes in the UI. I had all people in my research group testing different UI concepts and the current one is the one that ist most intuitive and straightforward to use.
If you disable the container, all its child widgets will be disabled. I don't think you can change this behaviour. So you have to disable separately all the child widgets that you don't want to remain enabled.
In the end I "solved" the problem by using a combination of QComboBox + QStackedWidget instead of a QToolBox and disable the combobox to lock it down.
You can lock the page of QToolbox using the UI function: setItemEnabled and pass the index of the page through the UI function: indexOf and your boolean.
Here's a little code:
ui->toolbox->setItemEnabled(ui->toolbox->indexOf(ui->page),false);
Related
[EDIT] - wasn't getting to the disable code the way I thought I was; works fine with any of the solutions below.
I have a modeless QT dialog, in which all kinds of user settings can be manipulated. In addition, it can save and load large data sets, which can take several seconds. Lots going on in the main window underneath (realtime app) and that's fine, and interaction there is okay, but in the dialog itself, I need to block user interaction.
Essentially, during the load, I don't want the user to be able to change / affect any of the controls in the modeless dialog (and there are a lot of them.)
Rather than disable each one individually (or even that way if that's the only way), is there a straightforward means I can use to disable input to the dialog entirely until the load is complete?
There's a progress bar in it that shows what's going on, too, which is constantly updated by the load process, so that needs to keep on working.
I tried this...
On dialog open:
QDialog *window = this;
Then around load:
window->setDisabled(true);
....
window->setDisabled(false);
...which compiles fine, and runs without complaint, but the window did not disable.
I also tried:
window->setEnabled(false);
....
window->setEnabled(true);
...that doesn't seem to do anything either.
I tried this too:
QList<QWidget*> list = window->findChildren<QWidget *>();
foreach(QWidget *qw, list)
{
qw->setEnabled(false);
}
....
QList<QWidget*> list = window->findChildren<QWidget *>();
foreach(QWidget *qw, list)
{
qw->setEnabled(true);
}
..also does nothing.
Based on Qt documentation (http://doc.qt.io/archives/qt-4.8/qwidget.html#enabled-prop):
Disabling a widget implicitly disables all its children. Enabling
respectively enables all child widgets unless they have been
explicitly disabled.
So your snippets are essentially doing same thing in different ways so that doesn't seem to be a problem and modality of dialog shouldn't have any effect on this as well. How are you loading / saving the data? If you are doing it in e.g. click slot then you are basically blocking the UI event loop which means that UI doesn't have cycles to react to your changes. If you want to isolate UI from background action so it will properly react even that you are doing heavy lifting in the background and you should consider using QRunnable or QTread to offload the work away from UI thread.
What would be the preferred (recommended) way to rearrange the components of a QML UI on an event such as a button click?
I do have designed a UI consisting of a bunch of custom components, each of them is bound to a (C++) model. I use anchors to position the components in the ApplicationWindow.
On a button click, I want to add an additional component which, due to its size, makes it necessary to rearrange the existing components. Actually, the button could be understood as switching into a different mode such as the Debug view in an IDE (think of Eclipse).
My goal is to keep the components in a consistent state between views and make the switch as fluent as possible.
I could think of these options:
Design two different views, and use a Loader or State to switch between them. As initializing the models takes some time, they should remain not be deleted during switching. I think, setting them as ContextProperty of the QMLApplicationEngine should take care of that.
Do a lot of rearranging in the onClicked()-Handler of the button. Probably the worst solution but does not require to reinitialize the components and models.
I think the most elegant solution would be to initialize the components in a some kind of container (or model) and then assign different layouts to this container on button click. But I have no idea, if this is possible and how to achieve that.
Edit: I followed approach 1. by using the StackLayout. It works basically, but it seems as if the invisible UI is still running in the background and consuming resources (e.g. requesting Images from my QQuickImageProvider).
Using a Loader eliminates this problem as it destroys the previous UI on every change of the source property. What I do like about the StackLayout however is that it preloads all UIs on app startup. So the transitions are smoother compared to the Loader. Is there a way to disable the invisible UIs?
Your option (1) is the one giving your the most flexibility, since you have two entirely separate UIs depending on state.
As you already discovered yourself this requires keeping all relevant state data in a way that is accessible by both UIs, e.g. in C++ or in globally accessible QML/Script objects.
I would go for that if you envision any more changes or tweaks than just rearranging elements.
If you are sure that rearranging elements will be sufficient, have a look at QML states
It is a bit like a combination of your options (2) and (3).
The state setup allows you very locally to define anchors and other properties for each element depending on a named state.
The button's signal handler then only needs to change which of the states is active, by assigning one of the names to the respective state property.
I am using the a ViewStack controlled with visibility of selected NavigatorContent being dependent on user selection of an option from a drop down menu.
Each View of the ViewStack has its own separate UI elements including 2-3 DataGrid, charts etc - think of it as a simple school application where each view binds to a course and shows performance of students for that course (while listing students in grid)
Sometimes, there is a problem with showing the data though - before the Rendering completes, the data is ready to be populated; this throws a null exception as the UI element where the data needs to be populated has not been created yet.
For this, I made the 'creationPolicy' set to 'all'. All works just fine after this property is set.
But there certainly are tonnes of performance issues:-
Even if the user never ever visits beyond the 1st visible view, the other views do get rendered (UI elements initialized and created).
Performance hit at startup - startup time is large and grows with the number of views I have (right now I have 9 views with creationPolicy set to all)!! Application was quick to load when only the 1st view was visible by default and creationPolicy was set to default/auto
UI kind of hangs/becomes unresponsive while application starts (as it all happens in the same thread)
What could be a possible solution to this.
These are the solutions that I had in mind, and which didn't work for a reason or two:-
For the first time a view is selected via the dropdown controller (i.e. when the rendering cum UI creation is yet to take place), I can show a preloader or sometime. I tried doing this, but the UI still hangs/becomes unresponsive.
CallLater can it help? Not really, as I would still be creating all views even if they are not required.
So, I need an elegant way of displaying the views (and show some sort of progress or loader) when they are created/instantiated.
Update
I get the Null errors when there is a sort of race condition - when the processing (which returns data to be filled into UI components, lets say a grid) completes before the rendering of the UI element completes - I have recognized why it happens. Initially, I had creationPolicy set to default, so whenever I use to select a view, it was created at that time; and in case the data to be populated was returned before the elements of the view were created there were null pointer (as the UI element I use to refer to were still be created and thus were null at that instance).
Now I am being forced to set the creationPolicy to all so that UI is created for all views and I fire the data processing on selection of that view from the dropdown.
What I'd rather like to do is to have a way to create the UI on demand (and not all of the UI even if it is not being used).
Maybe you shouldn't have the data processing push the results, but vice-versa, have the UI pull the data from the model once the UI controls are ready?
For example, have the data reside in ArrayCollections that you bind to DataGrids. That way, it doesn't matter who finishes first. Data generator doesn't even have to know who or where displays it, and the UI will show the data as soon as ArrayCollection signals that the data has changed.
I would suggest you use modules instead of view stack.
When modules are used separate swf files are created, and not loaded when the application is loaded. A module file is loaded only when it is called through moduleloader.load(module) method.
problem is I have a spark Tabbar, with many forms in each tab.
But I have a single global save button. Problem is, if I don't open a Tab,
it doesn't get initialized and therefore the forms it contains do not exist..
How Can I make it as if the user had clicked on every tab?
The best way to handle this is to have a data model that each tab is displaying and editing, rather than trying to go in and read the values out of the controls in each tab, and save those. This is at the heart of MVC.
However, if you're too invested in your current architecture to be able to change it and you are using a ViewStack with your TabBar, you may find that setting creationPolicy to "all" does what you want. If you're using States, I don't think you can force all of them to be instantiated without putting your application into each State at least once.
When I try to access the hidden TABs of my tab navigator control in action script, it returns a null error. But it works OK if I just activate the control in the user interface once. Obviously the control is not created until I use it. How do I make all the tabs automatically created by default ?
<mx:TabNavigator creationPolicy="all"/>
That should do it. Deferred instanciation is a feature, but sometimes it is a hassle.
The Flex framework is optimizing creation be default (creationPolicy="auto") so if you have a configuration dialog with a lot of tabs, for example, and the most useful tab is the first one, your application does not spend time and memory initializing the tabs that the user never sees.
This makes a lot of difference when dialogs like this never release, and is a good default to go with.
One thing to look at is using a private variable in your dialog/form instead of pushing the data to the control on the hidden page. This style treats the whole form as if it were a component, which it sort of is. To repeat: the MXML form/dialog/canvas is a class, and it can have data and methods in addition to containing other components.
Cheers
On a side note, I've run into the deferred-loading policy in a multi-state application, and circumvented it by forcing all elements to be included and invisible in the initial state. Something to consider, but only as a hack.