Qml ComboBox with TextField in Popup - qt

I have created a custom ComboBox that uses a ListView with a TextField in the footer, that's used to dynamically add options to the ComboBox.
The problem is, that as soon as the popup loses focus (so when the TextField receives focus), the popup gets closed.
I tried to force the popup to stay open, which does work, but then prevents the TextField from receiving focus (I guess because the popup regains focus as soon as open() is called).
ComboBox {
// ...
popup: Popup {
property bool forceOpen: false
onClosed: {
if(forceOpen)
open()
}
contentItem: ListView {
// ...
footer: TextField {
onPressed: forceOpen = true
}
}
}
}
I also tried all values for the closePolicy property of the Popup, but none of them helped.
I am using Qt5.11. The forceOpen solution used to work with Qt 5.10, but does not anymore.

Your problem should be fixed if you do not accept the focus on the ComboBox:
ComboBox {
focusPolicy: Qt.NoFocus
popup: Popup {
// ...
}
}

Related

QML Dynamic Load Window On Button Press, But Focus Instead If Exists

I have a button within my main ApplicationWindow (root) that dynamically loads and opens a second, different ApplicationWindow that is declared in a separate .qml file.
Button {
id: btnLogger
text: "Logger"
onClicked: {
var component = Qt.createComponent("logger.qml")
var window = component.createObject(logRoot)
window.show()
}
}
This works fine for opening a window when clicking the button. Subsequent clicks create further new windows.
My intent is that subsequent clicks should instead focus the preexisting window. If the new window is later closed, then clicking the button should revert back to opening the window.
i.e. if a window doesn't currently exist or exists but has been closed, create it and open it; else, focus it.
How would this be done from within qml? Alternatively, I am currently loading the application from a QQmlApplicationEngine in my C++, how could I use that to achieve this functionality?
The example code for my comment above:
Button {
id: btnLogger
text: "Logger"
property var wnd: undefined
onClicked: {
if(wnd == undefined)
{
var component = Qt.createComponent("logger.qml")
wnd = component.createObject(logRoot);
wnd.closing.connect(function() { btnLogger.wnd = undefined;});
wnd.show();
}
else
{
wnd.requestActivate();
}
}
}

How to have QML settings persistent for the application's lifetime

I'm trying to create warning dialog that has a "Do not prompt again" option. However, because it's part of a stack view the dialog gets destroyed regularly, and it's properties get destroyed too. How do I create settings that are persistent for the life of the application, but get reset back to a default value when the application gets started?
Item {
Button {
id: backButton
text: "Go Back"
// todo: Figure out how to set this just once at the start of the application
property bool promptOnClick: true
onClicked: promptOnClick ? cautionDialog.open() : stackView.pop()
}
Dialog {
id: cautionDialog
title: "Caution"
standardButtons: StandardButton.Ok | StandardButton.Cancel
onAccepted: {
if (checkboxDisablePrompt.checked) {
backButton.promptOnClick = false
}
stackView.pop()
}
Row {
CheckBox {
id: checkboxDisablePrompt
checked: false
anchors.verticalCenter: parent.verticalCenter
}
Label {
text: "Do not prompt again"
anchors.verticalCenter: parent.verticalCenter
}
}
}
}
If I'm understanding correctly, you want property values in the dialog to persist for the duration of the application.
A good option would be to write these as properties in item. The you can bind the correct properties in dialog to the ones you just created in item. Whenever you want to edit the property, edit it in the item.
Whenever the dialog is created, it will use the current values of the properties in item that it is bound too.

JavaFX alert dialog ignores the focused button

Why does the JavaFX alert dialog fires the Platform.exit(); when I press the Enter key even though the focused button in the alert dialog is Cancel?
soaStage.setOnCloseRequest(new EventHandler<WindowEvent>()
{
#Override
public void handle(WindowEvent event)
{
Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
alert.setTitle("Confirm");
alert.setHeaderText("Are you sure you want to exit?");
alert.setContentText("Press OK to exit, or Cancel to stay.");
alert.initOwner(soaStage);
Optional<ButtonType> result = alert.showAndWait();
if (result.get() == ButtonType.OK)
{
Platform.exit();
}
else
{
event.consume();
}
}
});
Default buttons are fired on enter
The OK button is fired when you press enter because it is a default button.
A default Button is the button that receives a keyboard VK_ENTER press, if no other node in the scene consumes it.
In the default JavaFX 8 Modena stylesheet, the default button is indicated by being a blue color rather than than the gray color of a standard button.
How to remove default button behaviour
You can remove this behavior from the alert dialog by not making the OK button a default button:
Button okButton = (Button) alert.getDialogPane().lookupButton(ButtonType.OK);
okButton.setDefaultButton(false);
I advise you not to do this, but instead to always leave a default button in alert dialogs.
On OS X, standard alert type dialogs have a default button which is triggered by enter even if another button is focused, so the standard behavior in JavaFX is consistent with that. Note: to allow this behavior in default dialogs in OS X it is necessary to enable full keyboard access.
If you do change the OK button to not be a default button, I suggest you change its text to something else (e.g. Exit for your case):
okButton.setText("Exit");
How to make enter fire focused buttons
Now, if you also want to make it so that the focused button fires when you press enter, then you can do this:
EventHandler<KeyEvent> fireOnEnter = event -> {
if (KeyCode.ENTER.equals(event.getCode())
&& event.getTarget() instanceof Button) {
((Button) event.getTarget()).fire();
}
};
DialogPane dialogPane = alert.getDialogPane();
dialogPane.getButtonTypes().stream()
.map(dialogPane::lookupButton)
.forEach(button ->
button.addEventHandler(
KeyEvent.KEY_PRESSED,
fireOnEnter
)
);
Note: In any case, focused buttons are always fired when you press space.
We can add ENTER binding to the whole buttons by creating a class that need to be instantiated once when the application starts.
public class EnableButtonEnterKey extends ButtonBehavior<Button> {
public EnableButtonEnterKey() {
super(new Button());
BUTTON_BINDINGS.add(new KeyBinding(ENTER, KEY_PRESSED, "Press"));
BUTTON_BINDINGS.add(new KeyBinding(ENTER, KEY_RELEASED, "Release"));
}
}
When starting the application, call
new EnableButtonEnterKey();
That's it.

How to know when the selected item has been changed in a QML QListView?

I'm using QtQuick 2.0 and and a QML ListView to display some items, and I need to know when the user chooses a different item. Emitting a signal when the user clicks a mouse area in the delegate works, i.e.
MouseArea{
onClicked: {
controller.itemChanged(model.item);
someList.currentIndex = index;
}
}
but only if the user uses the mouse to choose the item, but it doesn't work if the user uses the arrow keys.
I've been looking through the docs to find what signal is emitted when the currentIndex is changed, but I can't seem to find any. I'm looking for something similar to QListWidget::itemSelectionChanged() but it seems QML ListView doesn't have that.
You just need onCurrentItemChanged:{} in your ListView.
I ended up having to re-implement keyboard behaviour and exposing the model data from the delegate so I could fire the signal when a key is pressed.
ListView {
id: myList
focus: true
orientation: "Horizontal" //This is a horizontal list
signal itemChanged(var item)
interactive: false //Disable interactive so we can re-implement key behaviour
Keys.onPressed: {
if (event.key == Qt.Key_Left){
myList.decrementCurrentIndex(); //Change the current list selection
itemChanged(myList.currentItem.selectedItem.data); //Fire signal notifying that the selectedItem has changed
}
else if (event.key == Qt.Key_Right){
myList.incrementCurrentIndex(); //Change the current list selection
itemChanged(myList.currentItem.selectedItem.data); //Fire signal notifying that the selectedItem has changed
}
}
delegate: Component {
Rectangle {
id: myItem
property variant selectedItem: model //expose the model object
MouseArea {
anchors.fill: parent
onClicked: {
myList.currentIndex = index; //Change the current selected item to the clicked item
itemChanged(model.data); //Fire signal notifying that the selectedItem has changed
}
}
}
}
}
With this solution you have to manually change the item in QML whenever the user clicks an item or presses a key. I'm not sure this'd be an optimal solution with GridView but it works fine with ListView.
See this question. There are two approaches you can take
Connect to another component's event
Handle the event within that component
The signal handler is named on<SignalName> with the first letter of the signal in uppercase.

How to add an event handler for dynamically created QML elements?

I dynamically added some qml components to my gui according to this blog post. How can I add event handlers for those newly created components?
I'll explain with an example.
1)Create a custom button component as follows
//Button.qml ... This component's objects will be dynamically
// created
import QtQuick 2.1
Rectangle {
width: 100
height: 50
color:"blue"
//Since the buttons are created on the fly,
//we need to identify the button on which the user
// has clicked. The id must be unique
property string buttonId;
signal clicked(string buttonId);
MouseArea {
anchors.fill: parent
onClicked:parent.clicked(parent.buttonId)
}
}
This is a simple button which emits clicked signal on clicking on it..
Now lets create some buttons on the fly.
//Main.qml ... creates some buttons on the fly
import QtQuick 2.1
Rectangle{
id:root
width:500
height:500
function buttonClicked(buttonId)
{
console.debug(buttonId);
}
function createSomeButtons()
{
//Function creates 4 buttons
var component = Qt.createComponent("Button.qml");
for(var i=0;i<4;i++)
{
var buttonY = i*55; //Button height : 50 + 5 unit margin
var button = component.createObject(root,{"x":0,"y":buttonY,"buttonId":i+1});
//Connect the clicked signal of the newly created button
//to the event handler buttonClicked.
button.clicked.connect(buttonClicked)
}
}
Component.onCompleted: {
createSomeButtons();
}
}
Here when the Main.qml component creation has been completed, buttons are created.
4 buttons are created and after creation of each button, the javascript function buttonClicked is connected as event handler to the 'Button.qml''s clicked signal. Whenever the user clicks on the button, buttonClicked function will be called with buttonId as argument. You can do whatever you want in the event handler from here on.

Resources