Dynamically adjust the height of ListView in qml - qt

I am using qml to write a interface for Wifi connection. There are a list of Wifi names on the interface, which I use ListView to implement. When we click a Wifi name, a virtual keyboard will pop up and allow us to input the Wifi password. I hope I could shorten the height of wifi list during inputing, so that there are no overlaps between wifi list and virtual keyboard. Otherwise when we input the password for the last few entries in the wifi list, the virtual keyboard will hide the wifi name and input field. How could I achieve this?
Here is the code for the ListView:
ListView {
id: wifiList
width: parent.width
height: parent.height - connectedWifi.y - connectedWifi.height
visible: !wifiScanning.busy
clip: true
model: wifiModel
currentIndex: -1
onCountChanged: currentIndex=-1
delegate: WifiItemDelegate {
visible: connectedWifi.wifiName != ssid
height: connectedWifi.wifiName != ssid ? columnHeight:0
wifiErrorMessage: wifiErrorNotice
enterPasswordHint: wifiRoot.enterPasswordHint
}
}

I was using this in TableView. For ListView you can try;
rowDelegate: Rectangle {
color: styleData.selected ? 'skyblue' : (styleData.alternate 'whitesmoke' : 'white');
height:50
width: parent.width
}

Related

How to implement a list depending on the elements of a different QAbstractListModel in QML?

In QML, I want to specify a list of options (strings) that the user can choose to insert into my backend. But I don't want the list to contain strings that are already in the backend. And if the backend is updated, then the list of options should also update.
First I wrote a subclass QAbstractListModel which exposes a list of QString interface for my backend. It follows the guidelines from the docs, and exposes two custom functions push_back and indexOf.
class MyModel: public QAbstractListModel {
Q_OBJECT
public:
MyModel() { ... } // connect to the backend
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override {
Q_UNUSED(role);
if (!hasIndex(index.row(), index.column())) {
return {};
}
return ... // retreive backend object at index.row() and convert to QString
}
Q_INVOKABLE void push_back(const QString& item) {
beginInsertRows(QModelIndex(), int(_data->size()), int(_data->size()));
... // convert QString to backend data type and insert into backend
endInsertRows();
}
Q_INVOKABLE int indexOf(const QString& item) {
return ... // convert QString to backend type, and search backend for this object. return -1 if not found
}
private:
// pointer to backend object
};
I was thinking something like this in my QML. First a view of the strings that are already in the backend. This part works fine.
ListView {
anchors.fill: parent
spacing: 5
model: backend // an instance MyModel interface, passed in from main.cpp
delegate: Rectangle {
height: 25
width: 200
Text { text: model.display}
}
}
And then the list of options that can be added to the backend, which are not already part of the backend. This part doesn't work.
ListView {
anchors.fill: parent
spacing: 5
model: ["option1", "option2", "option3"]
delegate: Rectangle {
visible: backend.indexOf(model.modelData) >= 0
height: 25
width: 200
MouseArea {
id: mouseArea1
anchors.fill: parent
onClicked: {
backend.push_back(model.modelData)
}
}
Text { text: model.modelData }
}
}
The problem is, when strings are added to the backend, this list does not refresh. I don't think Qt understands that backend.indexOf needs to be recomputed whenever its modified.
You are correct about the problem. There's no binding that will re-call indexOf. One way to fix it is you could add a Connections object so you can listen for a specific signal and then manually update the visible property:
delegate: Rectangle {
visible: backend.indexOf(model.modelData) >= 0
height: 25
width: 200
MouseArea {
id: mouseArea1
anchors.fill: parent
onClicked: {
backend.push_back(model.modelData)
}
}
Text { text: model.modelData }
Connections {
target: backend
onCountChanged: visible = (backend.indexOf(model.modelData) >= 0)
}
}

How to get rid of handler dragging delay?

There's a range slider implementation. The problem is that when I press on a handler and start dragging, it doesn't move for a moment, therefore it happens some delay and distance between a handler and a mouse cursor.
Image { // handler's implementation
id: handler1
// ...
MouseArea {
anchors.fill: parent
drag.target: parent
drag.axis: Drag.XAxis
drag.minimumX: -width
drag.maximumX: handler2.x - parent.width
onPositionChanged: {
slider1 = (max - min) * (handler1.fakeX) / root.width + min
}
}
}
Could you please tell me how to deal with it?
You have to set the drag.threshold property of your MouseArea to 0 so that dragging starts immediately.

QQuickView, no keyboard focus after switching windows

My cpp application has a QMainWindow derived class, with QQuickView widget in the ui. Inside the view are a number of QML items which accept keyboard input. When an item is clicked, I call forceActiveFocus() on the clicked item. It all works from the time I launch the application, until the time I switch to another window.
If I switch to another application and back, or switch to another window within my application and back, calling forceActiveFocus() has no effect. The items are of a few different types, but here is a sample item:
TextInput {
id: textInput
anchors.fill: parent
inputMethodHints: Qt.ImhFormattedNumbersOnly
onActiveFocusChanged: console.log(activeFocus)
onEditingFinished:
{
}
MouseArea {
anchors.fill: textInput
onClicked: {
textInput.forceActiveFocus()
console.log("clicked")
}
}
}
When switching away I see activeFocus logged to the console as false. When I switch back again and click on the item, "clicked" is logged to the console, so the mouse event is being handled. However, onActiveFocusChanged is never called again.
I also tried an implementation with a FocusScope as the parent of the item, got the same behavior, with focus following my click until the point I switch away to some other window and back again.
UPDATE
After reading the comment from #user2436719, I've tried two minimal examples. It is only when using the QQuickView that this problem arises. Here is the QML app using a QML Window with the following main.qrc, which works just fine:
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
color: "lightblue"
Rectangle {
id: textInputRect
color: "white"
height: 50
width: 150
anchors.centerIn: parent
TextInput {
id: textInput
anchors.fill: parent
inputMethodHints: Qt.ImhFormattedNumbersOnly
onActiveFocusChanged: console.log(activeFocus)
onEditingFinished:
{
}
}
}
}
The second is a CPP application with a QMainWindow derived class that has a QQuickView widget in the ui. This version exhibits the problem behavior. Here is the main.qrc:
import QtQuick 2.7
import QtQuick.Window 2.2
Rectangle {
anchors.fill: parent
color: "lightblue"
Rectangle {
id: textInputRect
color: "white"
height: 50
width: 150
anchors.centerIn: parent
TextInput {
id: textInput
anchors.fill: parent
inputMethodHints: Qt.ImhFormattedNumbersOnly
onActiveFocusChanged: console.log(activeFocus)
onEditingFinished:
{
}
}
}
}
From the QQuickView version, here is main:
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QLatin1String("qrc:/main.qml")));
MainWindow window;
QQuickView* view = new QQuickView;
window.setView(view);
window.show();
return app.exec();
}
And here is how I set the QQuickView in the MainWindow:
void MainWindow::setView(QQuickView *value)
{
view = value;
QWidget *container = QWidget::createWindowContainer(view, this);
view->setSource(QUrl("qrc:/main.qml"));
ui->verticalLayout->addWidget(container);
}
Okay, this is QTBUG-34414. At the bottom of the comments for that bug a workaround is posted. The posted workaround fixed the problem for me, in the case of switching to other windows of my application, or to other applications. There was still no focus after closing dialogs. Putting this override in my window class fixed the problem for both cases:
bool MyWindow::event(QEvent *event)
{
if (event->type() == QEvent::ActivationChange ||
event->type() == QEvent::WindowUnblocked) {
if(view->isActive()) { //view is pointer to my QQuickView
window()->activateWindow();
return true;
}
}
// handle events that don't match
return QWidget::event(event);
}
This override worked for me with Qt 5.7 on OSX Sierra.

MenuBar like behaviour in QML

I want to have menubar like behaviour when clicked on a rectangle. Whenever a rectangle is clicked a model is updated and a ListView is shown. I want this ListView to disappear whenever another Rectangle is clicked and the listmodel should not be appended with each click. Here is my sample code.
Card.qml
Rectangle {
id: card
width: 50
height: 100
color: "pink"
Item {
id: rec
width: 50
anchors.bottom: parent.top
ListModel {
id: menuListModel
}
Component {
id: delegate
Rectangle {
width: 50
height: 20
color: "blue"
Text {
anchors.fill: parent
anchors.centerIn: parent
text: commandText
}
}
}
ListView {
anchors.fill: parent
model:menuListModel
delegate: delegate
interactive: false
}
}
MouseArea {
anchors.fill: parent
onClicked: {
rec.height += 40;
menuListModel.append({"commandText" : "Act"});
menuListModel.append({"commandText" : "Set"});
}
}
}
main.qml
Item {
width: 120
height: 200
Row {
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
spacing: 10
Card {
id: card1
}
Card {
id: card2
}
}
}
Also I want to call certain function upon clicking menu buttons i.e. Act and Set.
Edit
The following function is called with appropriate flags when a card(here rectangle) is clicked.
property int command_activate: 0x0001
property int command_summon: 0x0002
property int command_spsummon: 0x0004
property int command_mset: 0x0008
property int command_sset: 0x0010
property int command_repos: 0x0020
property int command_attack: 0x0040
property int command_list: 0x0080
function showMenu(flag) {
if(flag & command_activate) {
rec.height += 15;
menuListModel.append({"commandText" : "Activate"});
}
if(flag & command_summon) {
rec.height += 15;
menuListModel.append({"commandText" : "Normal Summon"});
}
if(flag & command_spsummon) {
rec.height += 15;
menuListModel.append({"commandText" : "Special Summon"});
}
if(flag & command_mset) {
rec.height += 15;
menuListModel.append({"commandText" : "Set"});
}
if(flag & command_sset) {
rec.height += 15;
menuListModel.append({"commandText" : "Set"});
}
if(flag & command_repos) {
if(position & pos_facedown) {
rec.height += 15;
menuListModel.append({"commandText" : "Flip Summon"});
}
else if(position & pos_attack) {
rec.height += 15;
menuListModel.append({"commandText" : "To Defense"});
}
else {
rec.height += 15;
menuListModel.append({"commandText" : "To Attack"});
}
}
if(flag & command_attack) {
rec.height += 15;
menuListModel.append({"commandText" : "Attack"});
}
if(flag & command_list) {
rec.height += 15;
menuListModel.append({"commandText" : "View"});
}
}
So, in short when a card is clicked a menu has to be shown according to the flag on the top of the card.
There are several problems with your code.
You cannot name your delegate "delegate". When you do this, the ListView uses its own delegate property to set itself, leading to nothing happening.
Also, why don't you just statically fill your ListView then use the visible property to toggle whether you display it or not? If you want it to disappear whenever another Card is clicked, you may have to use the focus property.
Indeed, setting focus to true will reset the focus of all other Items within the focus scope. Something like this might work:
Rectangle {
id: card
...
property alias model: list.model
Component {
id: mydelegate
Rectangle {
width: 50
height: 20
...
}
}
ListView {
id: list
visible: card.activeFocus
anchors.bottom: parent.top
width: card.width
delegate: mydelegate
interactive: false
height: childrenRect.height
}
MouseArea {
anchors.fill: parent
onClicked: {
card.focus = !card.focus
}
}
}
As for calling a function, you could add the name of the function to call directly in the ListModel. Add a MouseArea in your delegate, and send a signal on clicked. Then, you just have to call the matching slot (Agreed, the this[slot]() syntax is a bit hacky).
In Card.qml
Rectangle {
id: card
...
property alias model: list.model
signal itemClicked(string slot)
Component {
id: mydelegate
Rectangle {
...
MouseArea
{
anchors.fill: parent
onClicked: {
if(model.slot)
itemClicked(slot)
}
}
}
}
...
}
In main.qml
...
Card
{
model: ListModel {
ListElement{commandText: "Act"; slot: "act"}
ListElement{commandText: "Set"; slot: "set"}
}
function act()
{
print("act triggered")
}
function set()
{
print("set trigggered")
}
onItemClicked: { this[slot]() }
}
...
As I can't comment, to ask for clarification, I will state my assumptions from what I understood:
You have a number of ListModel that contain the information of the menu that should pop up, when clicking on the corresponding rectangle item in the bar.
The ListView that pops up shall disappear, once you clicked any other rectangle in this bar. (Also: if something outside the bar or menu is clicked? Or an item in the bar is selected?)
I'd go with only one ListView, and update/change the model, as well as the position (e.g. margin) once a button is clicked, so you don't have multiple unused objects flying around.
To not show anything, I think it is sufficient, to set an empty model.
You can also have a list of functions.
Row {
id: row
property var f: [a, b, c]
function a() { console.log('a'); }
function b() { console.log('b'); }
function c() { console.log('c'); }
width: 300
height: 50
Repeater {
id: rep
anchors.fill: parent
delegate: button
model: 3
Rectangle {
id: button
width: 98
height: 50
border.color: 'black'
Text {
text: "a"
anchors.fill: parent
}
MouseArea {
anchors.fill: parent
onClicked: row.f[index]()
}
}
}
}
e.g. will call the function a, b, or c - depending on the index of the rectangle.
I hope, so far, I could help.

Trouble getting 2 QML files to communicate using Signals

I have 2 qml files. Toolbarbutton.qml creates a button and DockWidget.qml creates a ListView. I am trying to have the button in Toolbarbutton broadcast to DockWidget that the button has been clicked. I then want to add items to my listView.
I have been trying to use a signal to establish the communication. In the Toolbarbutton.qml, I have a Rectangle item with an ID of saveButton. Under this Rectangle Item I added a signal called addSaveHistory(). I have shortened the code so its easier to see what I am doing.
Rectangle {
id: saveButton
width: 50
height: 30
border.color: "white"
color: buttonMouseArea.containsMouse ? "grey" : "black"
//Button text
Text{
id: buttonLabel
anchors.centerIn: parent
text: "Save +"
color: "white"
}
signal addSaveHistory(string url)
MouseArea{
id: buttonMouseArea
anchors.fill: parent //anchor the mousearea to the rect area
//onClicked handles valid mouse button clicks
onClicked: {
addSaveHistory("Hello?") //dispatch the event
}
}
}
In the DockWidget.qml file I have an Item that is using the Connections component. I have shorten the code so its easier to see what I am doing.
Item{
width: 100
height: 100
objectName: "Save + History"
Rectangle {
id: rect
anchors.fill: parent
color: "#323232"
Connections {
target: saveButton //id of rectangle in ToolbarButton.qml
onAddSaveHistory: {
// this is never called
}
}
}
I can't seem to figure out why the onAddSaveHistory is never called. I have also tried using a Loader component to load the Dockwidget.qml file into Toolbarbutton.qml and then specifically call a function in Dockwidget, but that doesn't work as well.
Do I just have the wrong idea of how the signals should work? I would greatly appreciation any guidance.
Here is my main.qml file.
import QtQuick 2.2
import Painter 1.0
import "save.js" as Save
Plugin {
//executed at startup
Component.onCompleted:{
//add toolbar
alg.ui.addToolBarWidget("ToolbarButton.qml")//add a button to toolbar
alg.ui.addDockWidget("DockWidget.qml")//add dock widget
Save.log("Incremental Save Plugin has been created")
}
}
First of all, realise that the signal is declared on the QML element at the root of the ToolbarButton.qml file, i.e. the Rectangle. Only the root item forms the interface for the QML element. All child elements are hidden to the outside world. So it's customary to declare the signal near the top of the file. A common ordering is:
Item {
id: root
property real foo: 0.0
property alias bar: innerItem.bar
signal baz(url location)
Rectangle {
id: innerItem
property color bar: "red"
}
}
So in this case a simple ToolbarButton.qml that declares a signal and emits it when you click in a contained MouseArea would look like this:
import QtQuick 2.3
Rectangle {
id: root
width: buttonLabel.width + 20
height: buttonLabel.height + 20
color: "steelBlue"
// Declare signal on the button object
signal addSaveHistory(string url)
Text {
id: buttonLabel
anchors.centerIn: parent
text: "Save +"
}
MouseArea {
id: buttonMouseArea
anchors.fill: parent
onClicked: {
// emit the signal
root.addSaveHistory("Hello?")
}
}
}
Then a simple consumer of this signal, here just a Text element but can be anything might look like this:
import QtQuick 2.3
Text {
id: dockWidget
text: "Waiting for a click..."
}
But wait, you say, how does that help. Well so far there is no connection. We make that when we instantiate our reciever item. So here, the main.qml file looks like this:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
// Instantiate the ToolbarButton that emits the signal when clicked
ToolbarButton {
id: toolbarButton
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.leftMargin: 10
}
// Instantiate the DockWidget (just a Text element in this example)
DockWidget {
id: dockWidget
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
anchors.left: parent.left
anchors.leftMargin: 10
// Complete the plumbing that connects the signal from item above
// with id: toolbarButton.
Connections {
target: toolbarButton
// When signal addSaveHistory is emitted,
// replace binding above with a new one
onAddSaveHistory: dockWidget.text = "Button was clicked"
}
}
}
In actual fact there is no need for the Connections element to be a child of the DockWidget. Think of the ToolbarButton and Dock widget as lego bricks that we are plumbing together at a location that has a higher level of knowledge about these items and how they should interact. If you want to though, you could wrap this up into its own more complicated custom QML component.
Furthermore, if you only have one thing caring about the signal, you don't even need the Connections element at all:
import QtQuick 2.3
import QtQuick.Window 2.2
Window {
visible: true
ToolbarButton {
id: toolbarButton
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: 10
anchors.leftMargin: 10
// Add signal handler directly to emitter
onAddSaveHistory: dockWidget.text = "Button was clicked"
}
DockWidget {
id: dockWidget
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
anchors.left: parent.left
anchors.leftMargin: 10
}
}
Hope this helps.

Resources